Python: Rechercher tous les anagrammes dans un texte

Selon wikipédia, une anagramme est un mot ou une expression obtenu en permutant les lettres d'un mot ou d'une expression de départ.

Pour l'exemple, je vais rechercher tous les anagrammes présents dans l'oeuvre de Jules Verne, "Le Tour Du Monde En 80 Jours".

Première étape, récupérer l'oeuvre au format txt.
Le site gutenberg permet de faire ce genre de choses.

Le module requests permet d'exécuter des requêtes HTTP

$ python3 -m pip install --upgrade requests
>>> import requests
>>> r1 = requests.get('https://www.gutenberg.org/files/800/800-8.txt')

Récupération du texte dans la réponse de la requête et nettoyage de tous les caractères non indispensables.
Le texte commence au 3 941 ème caractères et se termine au 431 252 ème caractères.
Tous les accents sont supprimés.
Tout le texte est converti en minuscule.
La regex recherche tous les mots de 3 caractères et plus.
Tous les mots en double sont supprimés.

Pour la suppression des caractères accentués, j'utilise le module removeaccents

$ python3 -m pip install --upgrade removeaccents
>>> from removeaccents.removeaccents import remove_accents
>>> text = r1.text[3941:431252]
>>> text = remove_accents(text)
>>> text = text.lower()
>>> words = re.findall(r'\b[A-Za-z]{3,}\b', text)
>>> words = list(set(words))

Nous nous retrouvons avec une liste de 8426 mots de 3 caractères et plus.

>>> len(words)
8426

Nous allons maintenant indexer tous les anagrammes dans un dictionnaire.

>>> anagrammes = dict()
>>> for word in words:
    letters = ''.join(sorted(word))
    if letters in anagrammes:
        anagrammes.get(letters).append(word)
    else:
        anagrammes[letters] = [word]
>>> anagrammes = {k: v for k, v in anagrammes.items() if len(v) > 1}

Nous obtenons une liste de 312 anagrammes

>>> len(anagrammes)
312

Voici la liste des 30 premiers anagrammes triée par ordre décroissant du nombre de mots.

>>> print(sorted(anagrammes.items(), key=lambda x: len(x[1]), reverse=True)[:30])
[('aerstu', ['sauter', 'autres', 'surate']), ('eeprst', ['pertes', 'pester', 'pretes']), ('eeelrv', ['releve', 'revele', 'elever']), ('aeenprst', ['esperant', 'presenta', 'separent']), ('aeiprt', ['partie', 'pirate', 'patrie']), ('efmnort', ['froment', 'forment', 'fremont']), ('aisv', ['vais', 'avis', 'visa']), ('aceinrt', ['crainte', 'ecriant', 'certain']), ('aceerss', ['sacrees', 'caresse', 'cessera']), ('aerrs', ['rares', 'raser', 'serra']), ('aeirstt', ['artiste, 'restait', 'attires']), ('eimprs', ['permis', 'mepris', 'primes']), ('eegnr', ['green', 'genre', 'gener']), ('adegr', ['garde', 'egard', 'grade']), ('eirsv', ['servi', 'viser', 'rives']), ('aeegilns', ['inegales', 'alignees', 'signalee']), ('aegnt', ['agent', 'geant', 'etang']), ('acert', ['ecart', 'trace', 'carte']), ('eeinrtt', ['interet', 'retenti', 'retient']), ('adegnrs', ['grandes', 'dangers', 'gardens']), ('eerrsv', ['verres', 'revers', 'verser']), ('aeirrtt', ['traiter', 'attirer', 'traitre']), ('acers', ['sacre', 'races', 'arecs']), ('ersu', ['user', 'rues', 'ruse']), ('cdeirt', ['decrit', 'credit', 'direct']), ('einru', ['nuire', 'ruine', 'reuni']), ('bceimno', ['combien', 'combine']), ('eirtv', ['vitre', 'revit']), ('aceimnoprt', ['importance', 'comprenait']), ('aceinrst', ['certains', 'craintes'])]