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 найпоширеніших слів у проекті Гутенберг станом на 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). Звідти ви можете просто розділити діапазон функції на три сегменти і назвати ті ваші труднощі.