1. Einleitung
Hier ist eine Möglichkeit, dieses Problem systematisch anzugehen: Wenn Sie einen Algorithmus haben, der Henker gut spielt, können Sie die Schwierigkeit jedes Wortes als die Anzahl der falschen Vermutungen ansehen, die Ihr Programm beim Erraten dieses Wortes annehmen würde.
2. Neben der Henker-Strategie
Es gibt eine Idee, die in einigen anderen Antworten und Kommentaren impliziert ist, dass die optimale Strategie für den Löser darin besteht, seine Entscheidungen auf die Häufigkeit von Buchstaben in Englisch oder auf die Häufigkeit von Wörtern in einem Korpus zu stützen. Dies ist eine verführerische Idee, aber nicht ganz richtig. Der Löser ist am besten geeignet, wenn er die Verteilung der vom Setter ausgewählten Wörter genau modelliert , und ein menschlicher Setter kann Wörter aufgrund ihrer Seltenheit oder Vermeidung häufig verwendeter Buchstaben auswählen. Obwohl zum Beispiel E
in Englisch die am häufigsten verwendeten Buchstaben sind, wenn der Einrichter wählt immer aus den Worten JUGFUL
, RHYTHM
, SYZYGY
, und ZYTHUM
dann ein perfekter Löser nicht startet durch Erraten E
!
Der beste Ansatz zur Modellierung des Setters hängt vom Kontext ab, aber ich denke, dass eine Art Bayes'sche induktive Inferenz in einem Kontext gut funktioniert, in dem der Solver viele Spiele gegen denselben Setter oder gegen eine Gruppe ähnlicher Setter spielt.
3. Ein Henker-Algorithmus
Hier werde ich einen Löser skizzieren, der ziemlich gut ist (aber alles andere als perfekt). Es modelliert den Setter so, dass er Wörter einheitlich aus einem festen Wörterbuch auswählt. Es ist ein gieriger Algorithmus : In jeder Phase wird der Buchstabe erraten, der die Anzahl der Fehler minimiert, dh Wörter, die die Vermutung nicht enthalten. Zum Beispiel, wenn keine Vermutungen wurden so weit, und die möglichen Worte sind gemacht DEED
, DEAD
und DARE
dann:
- Wenn Sie raten
D
oder E
, gibt es keine Fehler;
- Wenn Sie raten
A
, gibt es einen Fehler ( DEED
);
- Wenn Sie raten
R
, gibt es zwei Fehler ( DEED
und DEAD
);
- Wenn Sie einen anderen Buchstaben erraten, gibt es drei Fehler.
Also entweder D
oder E
ist eine gute Vermutung in dieser Situation.
(Dank an Colonel Panic in den Kommentaren für den Hinweis, dass korrekte Vermutungen in Henker frei sind - das habe ich bei meinem ersten Versuch völlig vergessen!)
4. Implementierung
Hier ist eine Implementierung dieses Algorithmus in 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. Beispielergebnisse
Mit dieser Strategie ist es möglich, die Schwierigkeit zu bewerten, jedes Wort in einer Sammlung zu erraten. Hier betrachte ich die Wörter mit sechs Buchstaben in meinem Systemwörterbuch:
>>> 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))
Die Wörter, die in diesem Wörterbuch am einfachsten zu erraten sind (zusammen mit der Reihenfolge der Vermutungen, die der Löser benötigt, um sie zu erraten), lauten wie folgt:
>>> 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')]
und die schwierigsten Wörter sind diese:
>>> 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')]
Der Grund dafür ist, dass Sie nach dem Erraten -UZZLE
noch sieben Möglichkeiten haben:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Wahl der Wortliste
Wenn Sie Wortlisten für Ihre Kinder erstellen, beginnen Sie natürlich nicht mit dem Systemwörterbuch Ihres Computers, sondern mit einer Liste von Wörtern, von denen Sie glauben, dass sie sie wahrscheinlich kennen. Sie können sich beispielsweise die Wiktionary-Listen der am häufigsten verwendeten Wörter in verschiedenen englischen Korpora ansehen .
Unter den 1.700 Wörtern mit sechs Buchstaben in den 10.000 häufigsten Wörtern in Project Gutenberg ab 2006 sind die schwierigsten zehn:
[(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 ist eine Figur in der Forsyte Saga von John Galsworthy . Die Wortliste wurde in Kleinbuchstaben umgewandelt, sodass ich Eigennamen nicht schnell entfernen konnte.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
. Von dort aus können Sie den Funktionsbereich einfach in drei Segmente aufteilen und diese als Ihre Schwierigkeiten bezeichnen.