1. Giới thiệu
Đây là một cách để tiếp cận vấn đề này một cách có hệ thống: nếu bạn có một thuật toán chơi tốt trò treo cổ, thì bạn có thể lấy độ khó của mỗi từ là số lần đoán sai mà chương trình của bạn sẽ thực hiện nếu đoán từ đó.
2. Bên cạnh chiến lược treo cổ
Có một ý tưởng tiềm ẩn trong một số câu trả lời và nhận xét khác, rằng chiến lược tối ưu cho người giải sẽ là dựa trên quyết định của họ về tần suất của các chữ cái trong tiếng Anh hoặc tần suất của các từ trong một số ngữ liệu. Đây là một ý tưởng quyến rũ, nhưng nó không hoàn toàn đúng. Bộ giải sẽ hoạt động tốt nhất nếu nó lập mô hình chính xác sự phân bố của các từ do người sắp xếp chọn và người sắp xếp là con người có thể đang chọn các từ dựa trên độ hiếm của chúng hoặc tránh các chữ cái thường dùng. Ví dụ, mặc dù E
là ký tự thường xuyên nhất được sử dụng bằng tiếng Anh, nếu setter luôn ưu tiên chọn từ các từ JUGFUL
, RHYTHM
, SYZYGY
, và ZYTHUM
, sau đó một người giải quyết hoàn hảo không bắt đầu bằng cách đoán E
!
Cách tiếp cận tốt nhất để lập mô hình setter phụ thuộc vào ngữ cảnh, nhưng tôi đoán rằng một số loại suy luận quy nạp Bayes sẽ hoạt động tốt trong bối cảnh mà người giải quyết chơi nhiều trò chơi với cùng một setter hoặc chống lại một nhóm các setter tương tự.
3. Một thuật toán treo cổ
Ở đây tôi sẽ phác thảo một bộ giải khá tốt (nhưng không hoàn hảo). Nó mô hình hóa bộ định nghĩa như việc chọn từ đồng nhất từ một từ điển cố định. Đó là một thuật toán tham lam : ở mỗi giai đoạn, nó đoán chữ cái để giảm thiểu số lần bỏ lỡ, tức là những từ không chứa câu đoán. Ví dụ, nếu không có phỏng đoán nào được đưa ra cho đến nay và các từ có thể là DEED
, DEAD
và DARE
, thì:
- nếu bạn đoán
D
hoặc E
, không có bỏ lỡ;
- nếu bạn đoán
A
, có một lần bỏ lỡ ( DEED
);
- nếu bạn đoán
R
, có hai lần bỏ lỡ ( DEED
và DEAD
);
- nếu bạn đoán bất kỳ chữ cái nào khác, có ba lần bỏ lỡ.
Vì vậy, một trong hai D
hoặc E
là một dự đoán tốt trong tình huống này.
(Cảm ơn Đại tá Panic trong các bình luận vì đã chỉ ra rằng các phỏng đoán đúng là miễn phí trong treo cổ — tôi hoàn toàn quên điều này trong lần thử đầu tiên của mình!)
4. Thực hiện
Đây là cách triển khai thuật toán này bằng 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. Kết quả ví dụ
Sử dụng chiến lược này, bạn có thể đánh giá độ khó đoán từng từ trong bộ sưu tập. Ở đây tôi xem xét các từ gồm sáu chữ cái trong từ điển hệ thống của mình:
>>> 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))
Các từ dễ đoán nhất trong từ điển này (cùng với chuỗi các phép đoán cần thiết để người giải đoán chúng) như sau:
>>> 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')]
và những từ khó nhất là:
>>> 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')]
Lý do mà những điều này khó là vì sau khi bạn đã đoán -UZZLE
, bạn vẫn còn bảy khả năng:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Lựa chọn danh sách từ
Tất nhiên khi chuẩn bị danh sách từ cho con bạn, bạn sẽ không bắt đầu với từ điển hệ thống của máy tính, bạn sẽ bắt đầu với danh sách các từ mà bạn nghĩ chúng có thể biết. Ví dụ, bạn có thể xem danh sách các từ được sử dụng thường xuyên nhất của Wiktionary trong các kho ngữ liệu tiếng Anh khác nhau.
Ví dụ, trong số 1.700 từ gồm 6 chữ cái trong 10.000 từ phổ biến nhất trong Project Gutenberg tính đến năm 2006 , thì 10 từ khó nhất là:
[(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 là một nhân vật trong Forsyte Saga của John Galsworthy ; danh sách từ đã được chuyển đổi thành chữ thường nên tôi không thể nhanh chóng xóa tên riêng.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
. Từ đó, bạn có thể chia phạm vi của chức năng thành ba phân đoạn và gọi đó là những khó khăn của bạn.