1. Introduction
Voici une façon d'aborder ce problème de manière systématique: si vous avez un algorithme qui joue bien au pendu, alors vous pouvez considérer la difficulté de chaque mot comme le nombre de fausses suppositions que votre programme prendrait si vous deviniez ce mot.
2. Mis à part la stratégie du bourreau
Il y a une idée implicite dans certaines autres réponses et commentaires, que la stratégie optimale pour le solveur serait de baser ses décisions sur la fréquence des lettres en anglais ou sur la fréquence des mots dans certains corpus. C'est une idée séduisante, mais ce n'est pas tout à fait juste. Le solveur réussit mieux s'il modélise avec précision la distribution des mots choisis par le setter , et un setter humain peut très bien choisir des mots en fonction de leur rareté ou de l'évitement des lettres fréquemment utilisées. Par exemple, bien que E
la lettre la plus fréquemment utilisée en anglais, si le compositeur choisit toujours des mots JUGFUL
, RHYTHM
, SYZYGY
et ZYTHUM
, puis un solveur parfait ne démarre pas en essayant de deviner E
!
La meilleure approche pour modéliser le setter dépend du contexte, mais je suppose qu'une sorte d'inférence inductive bayésienne fonctionnerait bien dans un contexte où le solveur joue de nombreux jeux contre le même setter, ou contre un groupe de setters similaires.
3. Un algorithme du pendu
Ici, je vais décrire un solveur assez bon (mais loin d'être parfait). Il modélise le passeur en choisissant les mots uniformément à partir d'un dictionnaire fixe. C'est un algorithme gourmand : à chaque étape, il devine la lettre qui minimise le nombre de ratés, c'est-à-dire les mots qui ne contiennent pas la supposition. Par exemple, si aucune estimation n'a été faite jusqu'à présent et que les mots possibles sont DEED
, DEAD
et DARE
, alors:
- si vous devinez
D
ou E
, il n'y a pas de ratés;
- si vous devinez
A
, il y a un manque ( DEED
);
- si vous devinez
R
, il y a deux ratés ( DEED
et DEAD
);
- si vous devinez une autre lettre, il y a trois échecs.
Donc, soit D
ou E
est une bonne estimation dans cette situation.
(Merci au colonel Panic dans ses commentaires pour avoir souligné que les suppositions correctes sont gratuites dans le bourreau - j'ai totalement oublié cela lors de ma première tentative!)
4. Mise en œuvre
Voici une implémentation de cet algorithme en Python:
from collections import defaultdict
from string import ascii_lowercase
def partition(guess, words):
"""Apply the single letter 'guess' to the sequence 'words' and return
a dictionary mapping the pattern of occurrences of 'guess' in a
word to the list of words with that pattern.
>>> words = 'deed even eyes mews peep star'.split()
>>> sorted(list(partition('e', words).items()))
[(0, ['star']), (2, ['mews']), (5, ['even', 'eyes']), (6, ['deed', 'peep'])]
"""
result = defaultdict(list)
for word in words:
key = sum(1 << i for i, letter in enumerate(word) if letter == guess)
result[key].append(word)
return result
def guess_cost(guess, words):
"""Return the cost of a guess, namely the number of words that don't
contain the guess.
>>> words = 'deed even eyes mews peep star'.split()
>>> guess_cost('e', words)
1
>>> guess_cost('s', words)
3
"""
return sum(guess not in word for word in words)
def word_guesses(words, wrong = 0, letters = ''):
"""Given the collection 'words' that match all letters guessed so far,
generate tuples (wrong, nguesses, word, guesses) where
'word' is the word that was guessed;
'guesses' is the sequence of letters guessed;
'wrong' is the number of these guesses that were wrong;
'nguesses' is len(guesses).
>>> words = 'deed even eyes heel mere peep star'.split()
>>> from pprint import pprint
>>> pprint(sorted(word_guesses(words)))
[(0, 1, 'mere', 'e'),
(0, 2, 'deed', 'ed'),
(0, 2, 'even', 'en'),
(1, 1, 'star', 'e'),
(1, 2, 'eyes', 'en'),
(1, 3, 'heel', 'edh'),
(2, 3, 'peep', 'edh')]
"""
if len(words) == 1:
yield wrong, len(letters), words[0], letters
return
best_guess = min((g for g in ascii_lowercase if g not in letters),
key = lambda g:guess_cost(g, words))
best_partition = partition(best_guess, words)
letters += best_guess
for pattern, words in best_partition.items():
for guess in word_guesses(words, wrong + (pattern == 0), letters):
yield guess
5. Exemples de résultats
En utilisant cette stratégie, il est possible d'évaluer la difficulté de deviner chaque mot d'une collection. Ici, je considère les mots de six lettres dans mon dictionnaire système:
>>> words = [w.strip() for w in open('/usr/share/dict/words') if w.lower() == w]
>>> six_letter_words = set(w for w in words if len(w) == 6)
>>> len(six_letter_words)
15066
>>> results = sorted(word_guesses(six_letter_words))
Les mots les plus faciles à deviner dans ce dictionnaire (ainsi que la séquence de suppositions nécessaires au solveur pour les deviner) sont les suivants:
>>> from pprint import pprint
>>> pprint(results[:10])
[(0, 1, 'eelery', 'e'),
(0, 2, 'coneen', 'en'),
(0, 2, 'earlet', 'er'),
(0, 2, 'earner', 'er'),
(0, 2, 'edgrew', 'er'),
(0, 2, 'eerily', 'el'),
(0, 2, 'egence', 'eg'),
(0, 2, 'eleven', 'el'),
(0, 2, 'enaena', 'en'),
(0, 2, 'ennead', 'en')]
et les mots les plus durs sont les suivants:
>>> pprint(results[-10:])
[(12, 16, 'buzzer', 'eraoiutlnsmdbcfg'),
(12, 16, 'cuffer', 'eraoiutlnsmdbpgc'),
(12, 16, 'jugger', 'eraoiutlnsmdbpgh'),
(12, 16, 'pugger', 'eraoiutlnsmdbpcf'),
(12, 16, 'suddle', 'eaioulbrdcfghmnp'),
(12, 16, 'yucker', 'eraoiutlnsmdbpgc'),
(12, 16, 'zipper', 'eraoinltsdgcbpjk'),
(12, 17, 'tuzzle', 'eaioulbrdcgszmnpt'),
(13, 16, 'wuzzer', 'eraoiutlnsmdbpgc'),
(13, 17, 'wuzzle', 'eaioulbrdcgszmnpt')]
La raison pour laquelle ils sont difficiles est qu'après avoir deviné -UZZLE
, il vous reste sept possibilités:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Choix de la liste de mots
Bien sûr, lors de la préparation de listes de mots pour vos enfants, vous ne commenceriez pas avec le dictionnaire système de votre ordinateur, vous commenceriez par une liste de mots que vous pensez qu'ils sont susceptibles de connaître. Par exemple, vous pouvez consulter les listes de Wiktionnaire des mots les plus fréquemment utilisés dans divers corpus anglais.
Par exemple, parmi les 1700 mots de six lettres dans les 10000 mots les plus courants du projet Gutenberg en 2006 , les dix les plus difficiles sont les suivants:
[(6, 10, 'losing', 'eaoignvwch'),
(6, 10, 'monkey', 'erdstaoync'),
(6, 10, 'pulled', 'erdaioupfh'),
(6, 10, 'slaves', 'erdsacthkl'),
(6, 10, 'supper', 'eriaoubsfm'),
(6, 11, 'hunter', 'eriaoubshng'),
(6, 11, 'nought', 'eaoiustghbf'),
(6, 11, 'wounds', 'eaoiusdnhpr'),
(6, 11, 'wright', 'eaoithglrbf'),
(7, 10, 'soames', 'erdsacthkl')]
(Soames Forsyte est un personnage de la saga Forsyte de John Galsworthy ; la liste de mots a été convertie en minuscules, il ne m'a donc pas été possible de supprimer rapidement les noms appropriés.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
. À partir de là, vous pouvez simplement diviser la gamme de la fonction en trois segments et les appeler vos difficultés.