1. Introducción
Aquí hay una manera de abordar este problema de manera sistemática: si tiene un algoritmo que juega bien al ahorcado, entonces puede tomar la dificultad de cada palabra como el número de conjeturas incorrectas que su programa tomaría si adivina esa palabra.
2. Aparte de la estrategia del ahorcado
Hay una idea implícita en algunas de las otras respuestas y comentarios, que la estrategia óptima para el solucionador sería basar sus decisiones en la frecuencia de las letras en inglés, o en la frecuencia de las palabras en algún corpus. Esta es una idea seductora, pero no del todo correcta. El solucionador lo hace mejor si modela con precisión la distribución de las palabras elegidas por el colocador , y un colocador humano puede estar eligiendo palabras basándose en su rareza o evitando las letras de uso frecuente. Por ejemplo, aunque E
es la letra más frecuentemente utilizado en Inglés, si el colocador elige siempre de las palabras JUGFUL
, RHYTHM
, SYZYGY
, y ZYTHUM
, a continuación, un programa de solución perfecta no se inicia al adivinar E
!
El mejor enfoque para modelar al colocador depende del contexto, pero supongo que algún tipo de inferencia inductiva bayesiana funcionaría bien en un contexto en el que el solucionador juega muchas partidas contra el mismo colocador o contra un grupo de armadores similares.
3. Un algoritmo del ahorcado
Aquí describiré un solucionador que es bastante bueno (pero lejos de ser perfecto). Modela al colocador eligiendo palabras uniformemente de un diccionario fijo. Es un algoritmo codicioso : en cada etapa adivina la letra que minimiza el número de errores, es decir, palabras que no contienen la conjetura. Por ejemplo, si no se han realizado conjeturas hasta ahora y las palabras posibles son DEED
, DEAD
y DARE
, entonces:
- si adivinas
D
o E
, no hay errores;
- si adivinas
A
, hay un error ( DEED
);
- si adivinas
R
, hay dos fallos ( DEED
y DEAD
);
- si adivina cualquier otra letra, hay tres fallos.
Entonces, D
o E
es una buena suposición en esta situación.
(Gracias al coronel Panic en los comentarios por señalar que las suposiciones correctas son gratis en el ahorcado; ¡lo olvidé por completo en mi primer intento!)
4. Implementación
Aquí hay una implementación de este algoritmo 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. Ejemplos de resultados
Con esta estrategia es posible evaluar la dificultad de adivinar cada palabra en una colección. Aquí considero las palabras de seis letras en el diccionario de mi sistema:
>>> 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))
Las palabras más fáciles de adivinar en este diccionario (junto con la secuencia de conjeturas necesarias para que el solucionador las adivine) son las siguientes:
>>> 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')]
y las palabras más difíciles son estas:
>>> 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 razón por la que estos son difíciles es porque después de haber adivinado -UZZLE
, todavía te quedan siete posibilidades:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Elección de lista de palabras
Por supuesto, al preparar listas de palabras para sus hijos, no comenzaría con el diccionario del sistema de su computadora, comenzaría con una lista de palabras que cree que es probable que conozcan. Por ejemplo, puede echar un vistazo a las listas de Wiktionary de las palabras más utilizadas en varios corpus en inglés.
Por ejemplo, entre las 1,700 palabras de seis letras en las 10,000 palabras más comunes en el Proyecto Gutenberg a partir de 2006 , las diez más difíciles son estas:
[(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 es un personaje de Forsyte Saga de John Galsworthy ; la lista de palabras se ha convertido a minúsculas, por lo que no me fue posible eliminar rápidamente los nombres propios).
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
. A partir de ahí, puede dividir el rango de la función en tres segmentos y llamarlos sus dificultades.