1. 소개
여기에이 문제에 체계적으로 접근하는 방법이 있습니다. 행맨을 잘하는 알고리즘이 있다면, 각 단어의 난이도를 그 단어를 추측 할 때 프로그램이 취할 수있는 잘못된 추측의 수로 삼을 수 있습니다.
2. 교수형 집행 전략에 제쳐두고
다른 답변과 댓글에 내재 된 아이디어가 있습니다. 솔버를위한 최적의 전략은 영어 글자의 빈도 또는 일부 말뭉치의 단어 빈도에 따라 결정을 내리는 것입니다. 이것은 매혹적인 생각이지만 옳지 않습니다. 솔버는 setter가 선택한 단어의 분포를 정확하게 모델링 하고, 인간 setter는 자주 사용되는 문자의 희소성 또는 회피를 기반으로 단어를 선택하는 것이 가장 좋습니다 . 하지만 예를 들어, E
영어에서 가장 자주 사용되는 문자입니다, 세터는 항상 단어에서 선택하는 경우 JUGFUL
, RHYTHM
, SYZYGY
, 그리고 ZYTHUM
다음, 완벽한 해결사는 추측에 의해 시작되지 않습니다 E
!
세터를 모델링하는 가장 좋은 방법은 컨텍스트에 따라 다르지만 어떤 종류의 베이지안 유도 추론은 솔버가 동일한 세터 또는 유사한 세터 그룹에 대해 많은 게임을하는 상황에서 잘 작동 할 것이라고 생각합니다.
3. 행맨 알고리즘
여기에서는 꽤 훌륭하지만 완벽하지는 않은 솔버를 간략하게 설명하겠습니다. 고정 사전에서 균일하게 단어를 선택하는 세터를 모델링합니다. 그것은이다 욕심 알고리즘 : 각 단계에서 그것이 미스, 추측를 포함하지 않는 단어의 수를 최소화하는 편지를 추측. 예를 들어 지금까지 추측이 이루어지지 않았고 가능한 단어가 DEED
, DEAD
및 DARE
이면 다음과 같습니다.
- 당신이 생각하는 경우
D
또는 E
, 더 미스 없다;
- 추측하면
A
하나의 미스 ( DEED
);
- 추측하면
R
두 가지 미스 ( DEED
및 DEAD
);
- 다른 문자를 맞히면 세 가지 누락이 있습니다.
따라서 D
또는 E
이 상황에서 좋은 추측입니다.
( 행맨에서 올바른 추측은 무료라는 점을 지적 해 주신 Panic 대령 에게 감사드립니다. 첫 시도에서 이것을 완전히 잊었습니다!)
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. 예제 결과
이 전략을 사용하면 컬렉션의 각 단어를 추측하는 어려움을 평가할 수 있습니다. 여기에서 시스템 사전의 6 자 단어를 고려합니다.
>>> 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
7 가지 가능성이 남아 있기 때문입니다 .
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. 단어 목록 선택
물론 자녀를위한 단어 목록을 준비 할 때 컴퓨터의 시스템 사전으로 시작하지 않을 것입니다. 자녀가 알 가능성이있는 단어 목록으로 시작합니다. 예를 들어, 다양한 영어 말뭉치에서 가장 자주 사용되는 단어 목록을 위키 셔 너리에서 살펴볼 수 있습니다 .
예를 들어, 2006 년 현재 구텐베르크 프로젝트에서 가장 많이 사용 되는 10,000 개의 단어 중 1,700 개의 6 글자 단어 중 가장 어려운 10 개 단어 는 다음과 같습니다.
[(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는 John Galsworthy 의 Forsyte Saga에 등장하는 캐릭터입니다 . 단어 목록이 소문자로 변환되어 고유 이름을 빨리 제거 할 수 없었습니다.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
입니다. 거기에서 함수의 범위를 3 개의 세그먼트로 나누고이를 어려움이라고 부를 수 있습니다.