L'algorithme du radoteur

Dans ce billet, nous allons implémenter l'algorithme du "radoteur", un algorithme attribué à Roland Moreno. Cet algorithme a pour but de générer, à partir d'un texte descriptif d'un produit, un nom susceptible d'être utilisé comme marque pour ce produit. L'intérêt conceptuel de l'algorithme est de produire une suite de caractères qui peut être vue comme la somme des mots qui le décrivent.

Ce billet se base sur l'explication de l'algorithme trouvée ici.

Description de l'algorithme

Pour fonctionner, l'algorithme a besoin de données d'entrée, c'est-à-dire une série de mots à partir de laquelle on veut en générer de nouveaux. Dans notre post nous utiliserons la chaîne de caractères suivante (tirée du document cité en introduction) :

SOIENT CINQUANTE MOTS D’ANGLAIS PRIS AU HASARD, SHANNON POSTULE L’EXISTENCE D’UNE LOI DE COMPOSITION DE CE SOUS-ENSEMBLE DE LA LANGUE, NON NEUTRE, ENTIÈREMENT DÉTERMINÉ, ET VA S’ATTACHER À DEMONTRER LA RIGUEUR DE CETTE LOI.

Ensuite, l'algorithme procède de la manière suivante :

  • initialisation : on tire un mot de la suite au hasard et on en garde la première lettre. Ceci définit en même temps une position donnée dans la chaîne, à partir de laquelle on va continuer à appliquer l'algorithme.
  • construction de caractères successifs
    • à partir de la position courante, on avance dans la chaîne jusqu'à rencontrer à nouveau la lettre actuelle
    • la prochaine lettre à garder est la lettre qui vient après la lettre trouvée
    • on reprend la recherche à partir de la position de la nouvelle lettre
  • l'algorithme termine quand le dernier caractère ajouté à notre suite est un espace, une virgule...

Implémentation

On définit tout d'abord la chaîne de caractères avec laquelle on travaille :

In [1]:
input_string = "SOIENT CINQUANTE MOTS D’ANGLAIS PRIS AU HASARD, SHANNON POSTULE L’EXISTENCE D’UNE LOI DE COMPOSITION DE CE SOUS-ENSEMBLE DE LA LANGUE, NON NEUTRE, ENTIÈREMENT DÉTERMINÉ, ET VA S’ATTACHER À DEMONTRER LA RIGUEUR DE CETTE LOI."

Ensuite, on choisit l'un des mots présents pour démarrer notre algorithme.

In [2]:
words = input_string.split(' ')
In [3]:
from random import randint
In [4]:
words[randint(0, len(words) - 1)]
Out[4]:
'CETTE'

On peut résumer ces étapes sous la forme d'une fonction :

In [5]:
def init_chain(input_string):
    """Retourne le caractère de départ et la position dans la chaîne, 
    choisis au hasard."""
    words = input_string.split(' ')
    r = randint(0, len(words) - 1)
    start_char = words[r][0]
    position = 0 + r + sum([len(w) for w in words[:r]]) 
    return start_char, position

On vérifie le fonctionnement de la fonction :

In [6]:
init_chain(input_string)
Out[6]:
('S', 48)
In [8]:
input_string[48]
Out[8]:
'S'

Comme on le voit, la lettre et la position se correspondent.

In [7]:
init_chain(input_string)
Out[7]:
('C', 7)
In [9]:
input_string[7]
Out[9]:
'C'

On peut maintenant implémenter la recherche du prochain caractère. Il suffit pour cela de démarrer la recherche de la prochaine occurence de la lettre courante à partir de la position actuelle.

In [13]:
start_char = 'C'
position = 7
In [21]:
while input_string[position + 1] != start_char:
    position = (position + 1) % len(input_string)
position
Out[21]:
72
In [24]:
input_string[73]
Out[24]:
'C'

Maintenant que les bases sont posées, nous pouvons résumer ceci sous forme d'une fonction :

In [42]:
def build_chain(input_string, start_char, position):
    """Construit une chaîne selon l'algorithme du radoteur."""
    chain = start_char
    while chain[-1] not in (' ', ',', '.') and len(chain) < 30:
        while input_string[(position + 1) % len(input_string)] != chain[-1]:
            position = (position + 1) % len(input_string)
        chain += input_string[(position + 2) % len(input_string)]
        position = (position + 2) % len(input_string)
    return chain 

Testons :

In [43]:
build_chain(input_string, 'S', 48)
Out[43]:
'STE '
In [44]:
input_string[48:]
Out[44]:
'SHANNON POSTULE L’EXISTENCE D’UNE LOI DE COMPOSITION DE CE SOUS-ENSEMBLE DE LA LANGUE, NON NEUTRE, ENTIÈREMENT DÉTERMINÉ, ET VA S’ATTACHER À DEMONTRER LA RIGUEUR DE CETTE LOI.'

On s'attend effectivement à la sortie 'STE' pour ces caractères de départ. L'algorithme est donc bien implémenté. A noter que l'on a ici utilisé deux subtilités dans le code :

  • une position modulo la longueur de la chaîne afin d'introduire un cycle dans la recherche des lettres
  • une limite de caractères de 30 pour éviter des chaînes trop longues

On peut maintenant générer pleins de mots de cette manière :

In [46]:
set([build_chain(input_string, *init_chain(input_string)) for i in range(5000)]) 
Out[46]:
{'ASHEMONCOSONE,',
 'CE ',
 'CENGURISIOUEUE ',
 'CHARENTETTR ',
 'CIS ',
 'D,',
 'DE ',
 'DE,',
 'DERI.',
 'DETENQU ',
 'DÉ,',
 'D’ENE ',
 'EMIGLEXI ',
 'EREUANN ',
 'HA ',
 'LA ',
 'LANGUTINTT ',
 'LAUL’USE ',
 'LE ',
 'LOINTS ',
 'LOMBLANONTE ',
 'MPRD’ACE ',
 'N ',
 'NT ',
 'POITRENÉT ',
 'PONS’AISANOSTIÈRMOIE ',
 'R ',
 'S ',
 'S-EMER ',
 'SOTUN ',
 'STE ',
 'VATA ',
 'À '}

On peut appliquer cette technique à d'autres ensemble. Par exemple pour trouver des noms de marques (comme on peut le lire sur la notice wikipédia de son inventeur). Imaginons que l'on cherche à créer un produit avec la description suivante :

Parfum capiteux, doux, subtil, idéal pour les soirées en ville.

On peut lui appliquer notre algorithme :

In [49]:
input_string = 'Parfum capiteux, doux, subtil, idéal pour les soirées en ville.'.upper()
In [50]:
set([build_chain(input_string, *init_chain(input_string)) for i in range(5000)]) 
Out[50]:
{'CALESURÉAR ',
 'DÉEN ',
 'E.',
 'IRFUX,',
 'LL,',
 'PAPOILEUX,',
 'PIL ',
 'S ',
 'VITIDOUM '}

Parmi ces propositions, on pourrait garder le Vitidoum, le Papoileux ou encore l'Irfux.

Conclusion

Nous avons implémenté l'algorithme du radoteur. Les résultats sont intéressants, mais pas forcéments tous valides. Je pense que ceci explique pourquoi cet algorithme ne peut fournir que des candidats qu'il faut ensuite trier manuellement pour retenir ceux qui sont pertinents.

Comments