Algo - jeux de dictionnaires, plus grand suffix commun

Links: notebook, html, PDF, python, slides, GitHub

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()

Enoncé

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(":", "").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']

Exercice 5 : expliquer pourquoi telle méthode est plus rapide.

La réponse devrait guider vers une méthode encore plus rapide.

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.

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'))

Exercice 2 : constuire un dictionnaire qui associe à chaque lettre tous les mots se terminant par celle-ci

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', "l'oméga"],
 'r': ['noir', 'jour', 'noir', 'autour', 'candeur'],
 'e': ['e',
  'rouge',
  'je',
  'quelque',
  'golfe',
  "d'ombre",
  'e',
  'lance',
  'rire',
  'colère',
  'que',
  "l'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',
  "d'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'],
 'g': ['sang'],
 'é': ['craché'],
 'x': ['paix', "d'animaux", 'paix', 'aux', 'studieux', 'yeux'],
 'n': ['clairon', 'plein', 'rayon']}

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.

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'))

Exercice 4 : mesurer le temps pris par chaque fonction

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.8341831999999996
debut = perf_counter()
for i in range(100):
    plus_grand_suffix_commun_dictionnaire(mots)
perf_counter() - debut
0.20950450000000043

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.