<jupyter_start><jupyter_text>Des dates qui font des nombres premiers (suite) ?Ce petit [notebook Jupyter](https://www.jupyter.org/), écrit en [Python](https://www.python.org/), a pour but de résoudre la question suivante :> *"Pour un jour fixé, en utilisant le jour le mois et les deux chiffres de l'année comme briques de base, et les opérations arithmétiques $+,\times,-,\mod,\%$, quel est le plus grand nombre premier que l'on peut obtenir ?"*Par exemple, en 2017, le 31 mai donne `31`, `5`, et `17`.- $31 \times 5 \times 17$ n'est évidemment pas premier,- $17 \times (31 \mod 5) = 17$ est premier.> Pour d'autres questions sur les dates et les nombres premiers, [ce premier notebook est aussi intéressant !](https://nbviewer.jupyter.org/github/Naereen/notebooks/blob/master/simus/Des_dates_qui_font_des_nombres_premiers.ipynb). ---- Une première solution, naïve- On va d'abord écrire (ou importer) une fonction pour tester si un entier est premier,- Puis on va écrire une fonction qui transforme une date en ses trois nombres,- Et une fonction qui essaie toutes les opérations possibles sur les trois nombres,- Et enfin une boucle sur les 365 (ou 366) jours de l'année suffira à afficher, pour chaque jour, le plus grand nombre premier obtenu.  Tester la primalité, version tricheur[`sympy`](http://www.sympy.org/) propose une fonction [`sympy.isprime`](http://docs.sympy.org/latest/modules/ntheory.htmlsympy.ntheory.primetest.isprime).<jupyter_code>from sympy import isprime<jupyter_output><empty_output><jupyter_text>Elle marche très bien, et est très rapide !<jupyter_code>[isprime(i) for i in [2, 3, 5, 7, 10, 11, 13, 17, 2017]]<jupyter_output><empty_output><jupyter_text>Pour des nombres de 8 chiffres (c'est tout petit), elle est vraiment rapide :<jupyter_code>from numpy.random import randint
%timeit sum([isprime(i) for i in randint(1e8, 1e9-1, 10**4)])<jupyter_output>63.6 ms ± 4.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)<jupyter_text>$\implies$ $65 ~\text{ms}$ pour 10000 nombres à tester, ça me semble assez rapide pour ce qu'on veut en faire ! ---- Transformer une date en nombreOn va utiliser le module [`datetime`](https://docs.python.org/3/library/datetime.html) de la bibliothèque standard :<jupyter_code>from datetime import datetime
today = datetime.today()
YEAR = today.year
print("On va travailler avec l'année", YEAR, "!")<jupyter_output>On va travailler avec l'année 2017 !<jupyter_text>C'est ensuite facile de transformer une date en nombre, selon les deux formats.On utilise [le formatage avec `.format()`](https://pyformat.info/datetime) (en Python 3) :<jupyter_code>def date_vers_nombre(date):
    day = int("{:%d}".format(date))
    month = int("{:%m}".format(date))
    year = int("{:%Y}".format(date)[-2:])
    return day, month, year
date = datetime(YEAR, 1, 12)
print(date_vers_nombre(date))<jupyter_output>(12, 1, 17)<jupyter_text>Tester toutes les opérations possibles- On utilise la fonction [`itertools.permutations`](https://docs.python.org/3/library/itertools.htmlitertools.permutation) pour obtenir les permutations des trois nombres $x,y,z$,- On applique les opérations dans l'ordre $f_1(f_2(x, y), z)$ et $f_1(x, f_2(y, z))$, ce qui suffit à couvrir tous les cas.- On utilise le module [`operator`](https://docs.python.org/3/library/operator.html) pour avoir des fonctions pour les opérations autorisées.<jupyter_code>from itertools import permutations
from operator import mod, mul, add, pow, sub, floordiv

operations = [mod, mul, add, sub, floordiv]
def tous_les_resultats(nombres, ops=operations):
    assert len(nombres) == 3
    tous = []
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                tous.append(f1(f2(x, y), z))
                tous.append(f1(x, f2(y, z)))
    # on enlève les doublons ici
    return list(set(tous))<jupyter_output><empty_output><jupyter_text>On peut la vérifier sur de petites entrées :<jupyter_code>tous_les_resultats([1, 2, 3], [add])
tous_les_resultats([1, 2, 3], [add, mul])<jupyter_output><empty_output><jupyter_text>On voit que stocker juste l'entier résultat ne suffit pas, on aimerait garder trace de chaque façon de l'obtenir !<jupyter_code>noms_operations = {
    mod: '%',
    mul: '*',
    add: '+',
    sub: '-',
    floordiv: '/',
}
def tous_les_resultats_2(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                n1 = f1(f2(x, y), z)
                s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                tous[s1] = n1
                n2 = f1(x, f2(y, z))
                s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                tous[s2] = n2
    return tous
tous_les_resultats_2([1, 2, 3], [add])
tous_les_resultats_2([1, 2, 3], [add, mul])<jupyter_output><empty_output><jupyter_text>Si on stocke avec comme clés les expressions, on va en avoir BEAUCOUP.Faisons l'inverse, avec le résultat de l'expression comme clés.<jupyter_code>def tous_les_resultats_3(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                n1 = f1(f2(x, y), z)
                s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                tous[n1] = s1
                n2 = f1(x, f2(y, z))
                s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                tous[n2] = s2
    return tous
tous_les_resultats_3([1, 2, 3], [add])
tous_les_resultats_3([1, 2, 3], [add, mul])<jupyter_output><empty_output><jupyter_text>Beaucoup plus raisonnable ! Ici, pour le 2ème exemple, le plus grand nombre premier obtenu est $7 = (3 \times 2) + 1$.<jupyter_code>def plus_grand_premier(nombres, ops=operations):
    tous = tous_les_resultats_3(nombres, ops=ops)
    premiers = [ p for p in tous.keys() if isprime(p) ]
    plus_grand_premier = max(premiers)
    expression = tous[plus_grand_premier]
    return plus_grand_premier, expression
plus_grand_premier([1, 2, 3], [add, mul])
plus_grand_premier([1, 2, 3])<jupyter_output><empty_output><jupyter_text>Il faut ignorer les erreurs de calculs et ne pas ajouter le nombre dans ce cas:<jupyter_code>def tous_les_resultats_4(nombres, ops=operations):
    assert len(nombres) == 3
    tous = {}
    for (x, y, z) in permutations(nombres):
        # on a un ordre pour x, y, z
        for f1 in ops:
            for f2 in ops:
                try:
                    n1 = f1(f2(x, y), z)
                    s1 = "{}({}({}, {}), {})".format(noms_operations[f1], noms_operations[f2], x, y, z)
                    tous[n1] = s1
                except:
                    pass
                try:
                    n2 = f1(x, f2(y, z))
                    s2 = "{}({}, {}({}, {}))".format(noms_operations[f1], x, noms_operations[f2], y, z)
                    tous[n2] = s2
                except:
                    pass
    return tous
def plus_grand_premier_2(nombres, ops=operations):
    tous = tous_les_resultats_4(nombres, ops=ops)
    premiers = [ p for p in tous.keys() if isprime(p) ]
    plus_grand_premier = max(premiers)
    expression = tous[plus_grand_premier]
    return plus_grand_premier, expression
plus_grand_premier_2([1, 2, 3], [add, mul])
plus_grand_premier_2([1, 2, 3])
plus_grand_premier_2([12, 1, 93])<jupyter_output><empty_output><jupyter_text>Tester sur un jour<jupyter_code>date
x, y, z = date_vers_nombre(date)
plus_grand_premier_2([x, y, z])<jupyter_output><empty_output><jupyter_text>Tester tous les jours de l'année On peut partir du 1er janvier de cette année, et ajouter des jours un par un.On utilise un itérateur (avec le mot clé `yield`), pour pouvoir facilement boucler sur tous les jours de l'année en cours :<jupyter_code>from datetime import timedelta

def tous_les_jours(year=YEAR):
    date = datetime(year, 1, 1)
    un_jour = timedelta(days=1)
    for i in range(0, 366):
        yield date
        date += un_jour
        if date.year > year:  # On est allé trop loin
            raise StopIteration
tous = []
for date in tous_les_jours():
    x, y, z = date_vers_nombre(date)
    p, expr = plus_grand_premier_2([x, y, z])
    tous.append(([x, y, z], p, expr))
    print("Pour la date {:%d-%m-%Y}, le plus grand nombre premier obtenu est {}, avec l'expression {}.".format(date, p, expr))
max(tous, key=lambda t: t[1])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1336092905599632, 'fraction_numerical': 0.025181096929975853, 'mean_word_length': 3.537297861241523, 'pattern_counts': {'":': 0, '<': 50, '<?xml version=': 0, '>': 53, 'https://': 7, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '13', 'dir': 'jupyter-structured-clean-dedup', 'id': '210614', 'license': 'MIT', 'n_tokens_mistral': 3305, 'n_tokens_neox': 3155, 'n_words': 912, 'path': 'simus/Des_dates_qui_font_des_nombres_premiers__suite.ipynb', 'repo_name': 'doc22940/notebooks-2', 'size': '8697'}