<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Algo - jeux de dictionnaires, plus grand suffixe commun
Les dictionnaires sont très utilisés pour associer des choses entre elles, surtout quand ces choses ne sont pas entières. Le notebook montre l'intérêt de perdre un peu de temps pour transformer les données et rendre un calcul plus rapide.
Step2:  Enoncé
Le texte suivant est un poème d'Arthur Rimbaud, Les Voyelles. On veut en extraire tous les mots.
Step3:  Exercice 1 
Step4:  Exercice 2 
Step5:  Exercice 3 
Step6:  Exercice 4 
Step7:  Exercice 5 
Step8:  C'est illisible. On ne montre que les mots se terminant par tes.
Step9:  Toujours pas très partique. On veut représenter l'arbre visuellement ou tout du moins une sous-partie. On utilise le langage DOT.
Step10:  Le résultat est différent car le dictionnaire ne garantit pas que les éléments seront parcourus dans l'ordre alphabétique.
<ASSISTANT_TASK:>
Python Code:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Explanation: Algo - jeux de dictionnaires, plus grand suffixe commun
Les dictionnaires sont très utilisés pour associer des choses entre elles, surtout quand ces choses ne sont pas entières. Le notebook montre l'intérêt de perdre un peu de temps pour transformer les données et rendre un calcul plus rapide.
End of explanation
poeme = 
A noir, E blanc, I rouge, U vert, O bleu, voyelles,
Je dirai quelque jour vos naissances latentes.
A, noir corset velu des mouches éclatantes
Qui bombillent autour des puanteurs cruelles,
Golfe d'ombre; E, candeur des vapeurs et des tentes,
Lance des glaciers fiers, rois blancs, frissons d'ombelles;
I, pourpres, sang craché, rire des lèvres belles
Dans la colère ou les ivresses pénitentes;
U, cycles, vibrements divins des mers virides,
Paix des pâtis semés d'animaux, paix des rides
Que l'alchimie imprime aux grands fronts studieux;
O, suprême clairon plein de strideurs étranges,
Silences traversés des Mondes et des Anges:
—O l'Oméga, rayon violet de Ses Yeux!
def extract_words(text):
    # ce n'est pas la plus efficace des fonctions mais ça fait ce qu'on veut
    spl = text.lower().replace("!", "").replace(",", "").replace(
        ";", "").replace(".", "").replace(":", "").replace("'", " ").split()
    return(spl)
    
print(extract_words(poeme))
Explanation: Enoncé
Le texte suivant est un poème d'Arthur Rimbaud, Les Voyelles. On veut en extraire tous les mots.
End of explanation
def plus_grand_suffix_commun(mots):
    longueur_max = max([len(m) for m in mots])
    meilleure_paire = None
    meilleur_suffix = None
    # On peut parcourir les tailles de suffixe dans un sens croissant
    # mais c'est plus efficace dans un sens décroissant dans la mesure
    # où le premier suffixe trouvé est alors nécessairement le plus long.
    for i in range(longueur_max - 1, 0, -1):
        for m1 in mots:
            for m2 in mots:  # ici, on pourrait ne parcourir qu'une partie des mots
                             # car m1,m2 ou m2,m1, c'est pareil.
                if m1 == m2:
                    continue
                if len(m1) < i or len(m2) < i:
                     continue
                suffixe = m1[-i:]
                if m2[-i:] == suffixe:
                    meilleur_suffix = suffixe
                    meilleure_paire = m1, m2
                    return meilleur_suffix, meilleure_paire
    
mots = extract_words(poeme)
plus_grand_suffix_commun(mots)
Explanation: Exercice 1 : trouver les deux mots qui partagent le plus grand suffixe en commun
Exercice 2 : constuire un dictionnaire qui associe à chaque lettre tous les mots se terminant par celle-ci
Exercice 3 : trouver les deux mots qui partagent le plus grand suffixe en commun en utilisant le dictionnaire précédent
Exercice 4 : mesurer le temps pris par chaque fonction
La fonction perf_counter est parfaite pour ça.
Exercice 5 : expliquer pourquoi telle méthode est plus rapide.
La réponse devrait guider vers une méthode encore plus rapide.
Exercice 6 : pousser l'idée plus loin et construire un trie
Indexer les mots par leur dernière lettre permet d'aller plus vite. Il faut maintenant trouver le suffixe le plus long dans chaque sous-groupe de mots. Ce problème est identique au précédent sur tous les mots précédents auxquels la dernière aurait été ôtée. Comment exploiter cette idée jusqu'au bout ?
Réponses
Exercice 1 : trouver les deux mots qui partagent le plus grand suffixe en commun
Ce n'est qu'une suggestion. La fonction repose sur trois boucles, la première parcourt différentes tailles de suffixe, les deux autres regardes toutes les paires de mots.
End of explanation
mots = extract_words(poeme)
suffix_map = {}
for mot in mots:
    lettre = mot[-1]
    if lettre in suffix_map:
        suffix_map[lettre].append(mot)
    else:
        suffix_map[lettre] = [mot]
suffix_map
Explanation: Exercice 2 : constuire un dictionnaire qui associe à chaque lettre tous les mots se terminant par celle-ci
End of explanation
def plus_grand_suffix_commun_dictionnaire(mots):
    suffix_map = {}
    for mot in mots:
        lettre = mot[-1]
        if lettre in suffix_map:
            suffix_map[lettre].append(mot)
        else:
            suffix_map[lettre] = [mot]
    tout = []
    for cle, valeur in suffix_map.items():
        suffix = plus_grand_suffix_commun(valeur)
        if suffix is None:
            continue
        tout.append((len(suffix[0]), suffix[0], suffix[1]))
    return max(tout)
mots = extract_words(poeme)
plus_grand_suffix_commun_dictionnaire(mots)
Explanation: Exercice 3 : trouver les deux mots qui partagent le plus grand suffixe en commun en utilisant le dictionnaire précédent
On reprend les deux ingrédients.
End of explanation
from time import perf_counter
mots = extract_words(poeme)
debut = perf_counter()
for i in range(100):
    plus_grand_suffix_commun(mots)
perf_counter() - debut
debut = perf_counter()
for i in range(100):
    plus_grand_suffix_commun_dictionnaire(mots)
perf_counter() - debut
Explanation: Exercice 4 : mesurer le temps pris par chaque fonction
End of explanation
def build_trie(liste):
    trie = {}  
    for mot in liste:
        noeud = trie
        for i in range(0, len(mot)):
            lettre = mot[len(mot) - i - 1]
            if lettre not in noeud:
                noeud[lettre] = {}
            noeud = noeud[lettre]
        noeud['FIN'] = 0
    return trie
liste = ['zabc', 'abc']
t = build_trie(liste)
t
mots = extract_words(poeme)
trie = build_trie(mots)
trie
Explanation: Exercice 5 : expliquer pourquoi telle méthode est plus rapide.
La seconde méthode est deux à trois fois plus rapide. Cela dépend du nombre de mots qu'on note N. Si on note L la longueur du plus grand mot, la première méthode a pour coût $O(LN^2)$. La seconde est une succession de deux étapes. La première étape construit un dictionnaire en parcourant une seule fois la liste des mots. Son coût est $O(N)$. La seconde utilise la première méthode mais sur des ensembles plus petits. Plus exactements, si $N_x$ est le nombre de mots se terminant pas $x$, alors le coût de la méthode est $O(L \sum_x N_x^2)$ avec $\sum_x N_x = N$. Il faut donc comparer $O(LN^2)$ à $O(N) + O(L \sum_x N_x^2)$. Le second coût est plus petit.
Exercice 6 : pousser l'idée plus loin et construire un trie
Un trie est une structure de données permettant de trouver rapidement tous les mots partageant le même préfixe ou suffixe.
End of explanation
trie['s']['e']['t']
Explanation: C'est illisible. On ne montre que les mots se terminant par tes.
End of explanation
def build_dot(trie, predecessor=None, root_name=None, depth=0):
    rows = []
    root = trie
    if predecessor is None:
        rows.append('digraph{')
        rows.append('%s%d [label="%s"];' % (
            root_name or 'ROOT', id(trie), root_name or 'ROOT'))
        rows.append(build_dot(trie, root_name or 'ROOT', depth=depth))
        rows.append("}")
    elif isinstance(trie, dict):
        for k, v in trie.items():
            rows.append('%s%d [label="%s"];' % (k, id(v), k))
            rows.append("%s%d -> %s%d;" % (predecessor, id(trie), k, id(v)))
            rows.append(build_dot(v, k, depth=depth+1))        
    return "\n".join(rows)
text = build_dot(trie['s']['e']['t'], root_name='set')
print(text)
from jyquickhelper import RenderJsDot
RenderJsDot(text, width="100%")
def plus_grand_suffix_commun_dictionnaire_trie(mots):
    whole_trie = build_trie(mots)
    
    def walk(trie):
        best = []
        for k, v in trie.items():
            if isinstance(v, int):
                continue
            r = walk(v)
            if len(r) > 0 and len(r) + 1 > len(best):
                best = [k] + r
        if len(best) > 0:
            return best
        if len(trie) >= 2:
            return ['FIN']
        return []
                   
    return walk(whole_trie)
res = plus_grand_suffix_commun_dictionnaire_trie(mots)
res
res = plus_grand_suffix_commun_dictionnaire(mots)
res
Explanation: Toujours pas très partique. On veut représenter l'arbre visuellement ou tout du moins une sous-partie. On utilise le langage DOT.
End of explanation
debut = perf_counter()
for i in range(100):
    plus_grand_suffix_commun_dictionnaire(mots)
perf_counter() - debut
debut = perf_counter()
for i in range(100):
    plus_grand_suffix_commun_dictionnaire_trie(mots)
perf_counter() - debut
Explanation: Le résultat est différent car le dictionnaire ne garantit pas que les éléments seront parcourus dans l'ordre alphabétique.
End of explanation
<END_TASK>