1. Введение
Вот способ подойти к этой проблеме систематически: если у вас есть алгоритм, который хорошо играет с палачом, то вы можете принять сложность каждого слова как количество ошибочных предположений, которые ваша программа примет, угадывая это слово.
2. Помимо стратегии палача
В некоторых других ответах и комментариях есть идея, что оптимальной стратегией для решателя было бы основывать свои решения на частоте букв в английском языке или на частоте слов в каком-то корпусе. Идея соблазнительная, но не совсем правильная. Решатель работает лучше всего, если он точно моделирует распределение слов, выбранных установщиком , а сеттер-человек вполне может выбирать слова на основе их редкости или избегая часто используемых букв. Например, хотя Eнаиболее часто используются письмо на английском языке, если сеттер всегда выбирает из слов JUGFUL, RHYTHM, SYZYGY, и ZYTHUM, то идеальный решатель не запускается гадать E!
Наилучший подход к моделированию сеттера зависит от контекста, но я предполагаю, что какой-то байесовский индуктивный вывод будет хорошо работать в контексте, когда решатель играет много игр против одного и того же сеттера или против группы похожих сеттеров.
3. Алгоритм палача
Здесь я обрисую довольно хороший (но далеко не идеальный) решатель. Он моделирует сеттер как единый выбор слов из фиксированного словаря. Это жадный алгоритм : на каждом этапе он угадывает букву, которая минимизирует количество промахов, то есть слова, не содержащие угадывания. Например, если до сих пор не было сделано никаких предположений, а возможные слова - DEED, DEADи DARE, то:
- если угадаете
Dили E, промахов нет;
- если угадаете
A, есть один промах ( DEED);
- если угадаете
R, есть два промаха ( DEEDи DEAD);
- если вы угадаете любую другую букву, будет три промаха.
Так что в данной ситуации можно предположить либо, Dлибо E.
(Спасибо полковнику Панику в комментариях за то, что он указал на то, что правильные догадки бесплатны в палачах - я совершенно забыл об этом с первой попытки!)
4. Реализация
Вот реализация этого алгоритма на 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. Примеры результатов
Используя эту стратегию, можно оценить сложность угадывания каждого слова в коллекции. Здесь я рассматриваю слова из шести букв в моем системном словаре:
>>> 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))
Проще всего угадывать слова в этом словаре (вместе с последовательностью догадок, необходимой решателю, чтобы угадать их) следующие:
>>> 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')]
и самые трудные слова такие:
>>> 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')]
Причина, по которой это сложно, заключается в том, что после того, как вы угадали -UZZLE, у вас все еще остается семь возможностей:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Выбор словаря
Конечно, при составлении списков слов для своих детей вы не начнете с системного словаря вашего компьютера, а начнете со списка слов, которые, по вашему мнению, они могут знать. Например, вы можете посмотреть в Викисловаре списки наиболее часто используемых слов в различных английских корпусах.
Например, среди 1700 шестибуквенных слов из 10 000 наиболее распространенных слов в Project Gutenberg по состоянию на 2006 г. самые трудные десять следующие:
[(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')]
(Сомс Форсайт - персонаж саги о Форсайтах Джона Голсуорси ; список слов был преобразован в нижний регистр, поэтому я не мог быстро удалить имена собственные.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency). Оттуда вы можете просто разделить диапазон функции на три сегмента и назвать их своими трудностями.