1. Perkenalan
Berikut adalah cara untuk mendekati masalah ini secara sistematis: jika Anda memiliki algoritme yang dapat memainkan algojo dengan baik, maka Anda dapat menganggap kesulitan setiap kata untuk menjadi jumlah tebakan yang salah yang akan dilakukan oleh program Anda jika menebak kata itu.
2. Selain strategi algojo
Ada gagasan yang tersirat dalam beberapa jawaban dan komentar lain, bahwa strategi optimal untuk pemecah adalah mendasarkan keputusan mereka pada frekuensi huruf dalam bahasa Inggris, atau pada frekuensi kata dalam beberapa korpus. Ini adalah ide yang menggoda, tapi kurang tepat. Pemecah bekerja paling baik jika secara akurat memodelkan distribusi kata yang dipilih oleh penyetel , dan penyetel manusia mungkin memilih kata berdasarkan kelangkaannya atau menghindari huruf yang sering digunakan. Sebagai contoh, meskipun E
adalah surat yang paling sering digunakan dalam bahasa Inggris, jika setter selalu memilih dari kata-kata JUGFUL
, RHYTHM
, SYZYGY
, dan ZYTHUM
, kemudian pemecah sempurna tidak mulai dengan menebak E
!
Pendekatan terbaik untuk memodelkan setter bergantung pada konteksnya, tetapi saya rasa beberapa jenis inferensi induktif Bayesian akan bekerja dengan baik dalam konteks di mana pemecah memainkan banyak permainan melawan setter yang sama, atau melawan sekelompok setter serupa.
3. Algoritma algojo
Di sini saya akan menjelaskan pemecah yang cukup bagus (tapi jauh dari sempurna). Ini memodelkan penyetel sebagai memilih kata-kata secara seragam dari kamus tetap. Ini adalah algoritma serakah : pada setiap tahap ia menebak huruf yang meminimalkan jumlah kesalahan, yaitu kata-kata yang tidak mengandung tebakan. Misalnya, jika sejauh ini tidak ada tebakan, dan kemungkinan kata-katanya adalah DEED
, DEAD
dan DARE
, maka:
- jika Anda menebak
D
atau E
, tidak ada kesalahan;
- jika Anda menebak
A
, ada satu kesalahan ( DEED
);
- jika Anda menebak
R
, ada dua kesalahan ( DEED
dan DEAD
);
- jika Anda menebak surat lain, ada tiga kesalahan.
Jadi, D
atau E
merupakan tebakan yang bagus dalam situasi ini.
(Terima kasih kepada Kolonel Panic dalam komentarnya karena menunjukkan bahwa tebakan yang benar gratis di hangman — saya benar-benar lupa ini dalam percobaan pertama saya!)
4. Implementasi
Berikut implementasi algoritma ini dengan 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. Contoh hasil
Menggunakan strategi ini dimungkinkan untuk mengevaluasi kesulitan menebak setiap kata dalam sebuah koleksi. Di sini saya mempertimbangkan kata enam huruf dalam kamus sistem saya:
>>> 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))
Kata-kata yang paling mudah ditebak dalam kamus ini (bersama dengan urutan tebakan yang dibutuhkan pemecah untuk menebaknya) adalah sebagai berikut:
>>> 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')]
dan kata-kata yang paling sulit adalah ini:
>>> 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')]
Alasan mengapa ini sulit adalah karena setelah Anda menebak -UZZLE
, Anda masih memiliki tujuh kemungkinan tersisa:
>>> ' '.join(sorted(w for w in six_letter_words if w.endswith('uzzle')))
'buzzle guzzle muzzle nuzzle puzzle tuzzle wuzzle'
6. Pilihan daftar kata
Tentu saja saat menyiapkan daftar kata untuk anak-anak Anda, Anda tidak akan memulai dengan kamus sistem komputer Anda, Anda akan mulai dengan daftar kata yang menurut Anda mungkin mereka ketahui. Misalnya, Anda mungkin melihat daftar kata-kata yang paling sering digunakan di berbagai korpora bahasa Inggris di Wiktionary .
Misalnya, di antara 1.700 kata enam huruf dalam 10.000 kata yang paling umum di Proyek Gutenberg pada tahun 2006 , sepuluh kata yang paling sulit adalah:
[(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 adalah karakter dalam Forsyte Saga oleh John Galsworthy ; daftar kata telah diubah menjadi huruf kecil jadi tidak mungkin bagi saya untuk menghapus nama yang tepat dengan cepat.)
f(w) = (# unique letters) * (7 - # vowels) * (sum of the positions of unique letters in a list, ordered by frequency)
. Dari sana, Anda dapat membagi rentang fungsi menjadi tiga segmen dan menyebutnya sebagai kesulitan Anda.