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.
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Le texte suivant est un poème d'Arthur Rimbaud, Les Voyelles. On veut en extraire tous les mots.
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))
['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']
La fonction perf_counter est parfaite pour ça.
La réponse devrait guider vers une méthode encore plus rapide.
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 ?
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.
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)
('tentes', ('latentes', 'tentes'))
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
{'a': ['a', 'a', 'la', 'oméga'], 'r': ['noir', 'jour', 'noir', 'autour', 'candeur'], 'e': ['e', 'rouge', 'je', 'quelque', 'golfe', 'ombre', 'e', 'lance', 'rire', 'colère', 'que', 'alchimie', 'imprime', 'suprême', 'de', 'de'], 'c': ['blanc'], 'i': ['i', 'dirai', 'qui', 'i'], 'u': ['u', 'bleu', 'velu', 'ou', 'u'], 't': ['vert', 'corset', 'bombillent', 'et', 'et', 'violet'], 'o': ['o', 'o', '—o'], 's': ['voyelles', 'vos', 'naissances', 'latentes', 'des', 'mouches', 'éclatantes', 'des', 'puanteurs', 'cruelles', 'des', 'vapeurs', 'des', 'tentes', 'des', 'glaciers', 'fiers', 'rois', 'blancs', 'frissons', 'ombelles', 'pourpres', 'des', 'lèvres', 'belles', 'dans', 'les', 'ivresses', 'pénitentes', 'cycles', 'vibrements', 'divins', 'des', 'mers', 'virides', 'des', 'pâtis', 'semés', 'des', 'rides', 'grands', 'fronts', 'strideurs', 'étranges', 'silences', 'traversés', 'des', 'mondes', 'des', 'anges', 'ses'], 'd': ['d', 'd', 'd'], 'g': ['sang'], 'é': ['craché'], 'x': ['paix', 'animaux', 'paix', 'aux', 'studieux', 'yeux'], 'l': ['l', 'l'], 'n': ['clairon', 'plein', 'rayon']}
On reprend les deux ingrédients.
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)
(6, 'tentes', ('latentes', 'tentes'))
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
0.4579025000000172
debut = perf_counter()
for i in range(100):
plus_grand_suffix_commun_dictionnaire(mots)
perf_counter() - debut
0.15045649999999
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.
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
{'c': {'b': {'a': {'z': {'FIN': 0}, 'FIN': 0}}}}
mots = extract_words(poeme)
trie = build_trie(mots)
trie
{'a': {'FIN': 0, 'l': {'FIN': 0}, 'g': {'é': {'m': {'o': {'FIN': 0}}}}}, 'r': {'i': {'o': {'n': {'FIN': 0}}}, 'u': {'o': {'j': {'FIN': 0}, 't': {'u': {'a': {'FIN': 0}}}}, 'e': {'d': {'n': {'a': {'c': {'FIN': 0}}}}}}}, 'e': {'FIN': 0, 'g': {'u': {'o': {'r': {'FIN': 0}}}}, 'j': {'FIN': 0}, 'u': {'q': {'l': {'e': {'u': {'q': {'FIN': 0}}}}, 'FIN': 0}}, 'f': {'l': {'o': {'g': {'FIN': 0}}}}, 'r': {'b': {'m': {'o': {'FIN': 0}}}, 'i': {'r': {'FIN': 0}}, 'è': {'l': {'o': {'c': {'FIN': 0}}}}}, 'c': {'n': {'a': {'l': {'FIN': 0}}}}, 'i': {'m': {'i': {'h': {'c': {'l': {'a': {'FIN': 0}}}}}}}, 'm': {'i': {'r': {'p': {'m': {'i': {'FIN': 0}}}}}, 'ê': {'r': {'p': {'u': {'s': {'FIN': 0}}}}}}, 'd': {'FIN': 0}}, 'c': {'n': {'a': {'l': {'b': {'FIN': 0}}}}}, 'i': {'FIN': 0, 'a': {'r': {'i': {'d': {'FIN': 0}}}}, 'u': {'q': {'FIN': 0}}}, 'u': {'FIN': 0, 'e': {'l': {'b': {'FIN': 0}}}, 'l': {'e': {'v': {'FIN': 0}}}, 'o': {'FIN': 0}}, 't': {'r': {'e': {'v': {'FIN': 0}}}, 'e': {'s': {'r': {'o': {'c': {'FIN': 0}}}}, 'FIN': 0, 'l': {'o': {'i': {'v': {'FIN': 0}}}}}, 'n': {'e': {'l': {'l': {'i': {'b': {'m': {'o': {'b': {'FIN': 0}}}}}}}}}}, 'o': {'FIN': 0, '—': {'FIN': 0}}, 's': {'e': {'l': {'l': {'e': {'y': {'o': {'v': {'FIN': 0}}}, 'u': {'r': {'c': {'FIN': 0}}}, 'b': {'m': {'o': {'FIN': 0}}, 'FIN': 0}}}, 'FIN': 0, 'c': {'y': {'c': {'FIN': 0}}}}, 'c': {'n': {'a': {'s': {'s': {'i': {'a': {'n': {'FIN': 0}}}}}}, 'e': {'l': {'i': {'s': {'FIN': 0}}}}}}, 't': {'n': {'e': {'t': {'a': {'l': {'FIN': 0}}, 'FIN': 0, 'i': {'n': {'é': {'p': {'FIN': 0}}}}}}, 'a': {'t': {'a': {'l': {'c': {'é': {'FIN': 0}}}}}}}}, 'd': {'FIN': 0, 'i': {'r': {'i': {'v': {'FIN': 0}}, 'FIN': 0}}, 'n': {'o': {'m': {'FIN': 0}}}}, 'h': {'c': {'u': {'o': {'m': {'FIN': 0}}}}}, 'r': {'p': {'r': {'u': {'o': {'p': {'FIN': 0}}}}}, 'v': {'è': {'l': {'FIN': 0}}}}, 's': {'s': {'e': {'r': {'v': {'i': {'FIN': 0}}}}}, 'FIN': 0}, 'g': {'n': {'a': {'r': {'t': {'é': {'FIN': 0}}}, 'FIN': 0}}}}, 'o': {'v': {'FIN': 0}}, 'r': {'u': {'e': {'t': {'n': {'a': {'u': {'p': {'FIN': 0}}}}}, 'p': {'a': {'v': {'FIN': 0}}}, 'd': {'i': {'r': {'t': {'s': {'FIN': 0}}}}}}}, 'e': {'i': {'c': {'a': {'l': {'g': {'FIN': 0}}}}, 'f': {'FIN': 0}}, 'm': {'FIN': 0}}}, 'i': {'o': {'r': {'FIN': 0}}, 't': {'â': {'p': {'FIN': 0}}}}, 'c': {'n': {'a': {'l': {'b': {'FIN': 0}}}}}, 'n': {'o': {'s': {'s': {'i': {'r': {'f': {'FIN': 0}}}}}}, 'a': {'d': {'FIN': 0}}, 'i': {'v': {'i': {'d': {'FIN': 0}}}}}, 't': {'n': {'e': {'m': {'e': {'r': {'b': {'i': {'v': {'FIN': 0}}}}}}}, 'o': {'r': {'f': {'FIN': 0}}}}}, 'é': {'m': {'e': {'s': {'FIN': 0}}}, 's': {'r': {'e': {'v': {'a': {'r': {'t': {'FIN': 0}}}}}}}}, 'd': {'n': {'a': {'r': {'g': {'FIN': 0}}}}}}, 'd': {'FIN': 0}, 'g': {'n': {'a': {'s': {'FIN': 0}}}}, 'é': {'h': {'c': {'a': {'r': {'c': {'FIN': 0}}}}}}, 'x': {'i': {'a': {'p': {'FIN': 0}}}, 'u': {'a': {'m': {'i': {'n': {'a': {'FIN': 0}}}}, 'FIN': 0}, 'e': {'i': {'d': {'u': {'t': {'s': {'FIN': 0}}}}}, 'y': {'FIN': 0}}}}, 'l': {'FIN': 0}, 'n': {'o': {'r': {'i': {'a': {'l': {'c': {'FIN': 0}}}}}, 'y': {'a': {'r': {'FIN': 0}}}}, 'i': {'e': {'l': {'p': {'FIN': 0}}}}}}
C'est illisible. On ne montre que les mots se terminant par tes
.
trie['s']['e']['t']
{'n': {'e': {'t': {'a': {'l': {'FIN': 0}}, 'FIN': 0, 'i': {'n': {'é': {'p': {'FIN': 0}}}}}}, 'a': {'t': {'a': {'l': {'c': {'é': {'FIN': 0}}}}}}}}
Toujours pas très partique. On veut représenter l'arbre visuellement ou tout du moins une sous-partie. On utilise le langage DOT.
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)
digraph{ set2188668603648 [label="set"]; n2188668603008 [label="n"]; set2188668603648 -> n2188668603008; e2188668601536 [label="e"]; n2188668603008 -> e2188668601536; t2188668603136 [label="t"]; e2188668601536 -> t2188668603136; a2188668602112 [label="a"]; t2188668603136 -> a2188668602112; l2188668602944 [label="l"]; a2188668602112 -> l2188668602944; FIN2188593162512 [label="FIN"]; l2188668602944 -> FIN2188593162512; FIN2188593162512 [label="FIN"]; t2188668603136 -> FIN2188593162512; i2188668883392 [label="i"]; t2188668603136 -> i2188668883392; n2188668883456 [label="n"]; i2188668883392 -> n2188668883456; é2188668883520 [label="é"]; n2188668883456 -> é2188668883520; p2188668883584 [label="p"]; é2188668883520 -> p2188668883584; FIN2188593162512 [label="FIN"]; p2188668883584 -> FIN2188593162512; a2188668617536 [label="a"]; n2188668603008 -> a2188668617536; t2188668618304 [label="t"]; a2188668617536 -> t2188668618304; a2188668617856 [label="a"]; t2188668618304 -> a2188668617856; l2188668363712 [label="l"]; a2188668617856 -> l2188668363712; c2188668659456 [label="c"]; l2188668363712 -> c2188668659456; é2188667556608 [label="é"]; c2188668659456 -> é2188667556608; FIN2188593162512 [label="FIN"]; é2188667556608 -> FIN2188593162512; }
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
['s', 'e', 'l', 'l', 'e', 'b', 'FIN']
res = plus_grand_suffix_commun_dictionnaire(mots)
res
(6, 'tentes', ('latentes', 'tentes'))
Le résultat est différent car le dictionnaire ne garantit pas que les éléments seront parcourus dans l'ordre alphabétique.
debut = perf_counter()
for i in range(100):
plus_grand_suffix_commun_dictionnaire(mots)
perf_counter() - debut
0.18440039999998703
debut = perf_counter()
for i in range(100):
plus_grand_suffix_commun_dictionnaire_trie(mots)
perf_counter() - debut
0.057187499999997726
Mais c'est beaucoup plus rapide.