Saya pikir Anda mungkin akan menghabiskan sebagian besar waktu Anda mencoba mencocokkan kata-kata yang tidak mungkin dibangun oleh kotak surat Anda. Jadi, hal pertama yang akan saya lakukan adalah mencoba mempercepat langkah itu dan itu akan membuat Anda mendapatkan sebagian besar perjalanan ke sana.
Untuk ini, saya akan menyatakan kembali kotak sebagai tabel kemungkinan "bergerak" yang Anda indeks dengan transisi surat yang Anda lihat.
Mulailah dengan memberi setiap huruf suatu angka dari seluruh alfabet Anda (A = 0, B = 1, C = 2, ... dan seterusnya).
Mari kita ambil contoh ini:
h b c d
e e g h
l l k l
m o f p
Dan untuk sekarang, mari kita gunakan alfabet dari surat-surat yang kita miliki (biasanya Anda mungkin ingin menggunakan seluruh alfabet yang sama setiap waktu):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Kemudian Anda membuat array boolean 2D yang memberi tahu apakah Anda memiliki transisi huruf tertentu yang tersedia:
| 0 1 2 3 4 5 6 7 8 9 10 11 <- from letter
| b c d e f g h k l m o p
-----+--------------------------------------
0 b | T T T T
1 c | T T T T T
2 d | T T T
3 e | T T T T T T T
4 f | T T T T
5 g | T T T T T T T
6 h | T T T T T T T
7 k | T T T T T T T
8 l | T T T T T T T T T
9 m | T T
10 o | T T T T
11 p | T T T
^
to letter
Sekarang, lihat daftar kata-kata Anda dan ubah kata-katanya menjadi transisi:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Kemudian periksa apakah transisi ini diizinkan dengan melihatnya di tabel Anda:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Jika mereka semua diizinkan, ada kemungkinan kata ini dapat ditemukan.
Misalnya kata "helm" dapat dikesampingkan pada transisi ke-4 (m ke e: helMEt), karena entri di tabel Anda salah.
Dan kata hamster dapat dikesampingkan, karena transisi pertama (h ke a) tidak diperbolehkan (bahkan tidak ada di meja Anda).
Sekarang, untuk kata-kata tersisa yang mungkin sangat sedikit yang tidak Anda hilangkan, cobalah untuk benar-benar menemukannya di grid seperti yang Anda lakukan sekarang atau seperti yang disarankan dalam beberapa jawaban lain di sini. Ini untuk menghindari kesalahan positif yang dihasilkan dari lompatan di antara huruf-huruf identik di kisi Anda. Misalnya kata "bantuan" diizinkan oleh tabel, tetapi tidak oleh grid.
Beberapa tips peningkatan kinerja lebih lanjut tentang ide ini:
Alih-alih menggunakan array 2D, gunakan array 1D dan cukup hitung sendiri indeks huruf kedua. Jadi, alih-alih array 12x12 seperti di atas, buat array 1D dengan panjang 144. Jika Anda selalu menggunakan alfabet yang sama (yaitu array 26x26 = 676x1 untuk alfabet bahasa Inggris standar), bahkan jika tidak semua huruf muncul di kisi Anda , Anda dapat melakukan pra-komputasi indeks ke dalam array 1D ini yang perlu Anda uji agar sesuai dengan kata-kata kamus Anda. Sebagai contoh, indeks untuk 'halo' pada contoh di atas adalah
hello (6, 3, 8, 8, 10):
42 (from 6 + 3x12), 99, 104, 128
-> "hello" will be stored as 42, 99, 104, 128 in the dictionary
Perluas ide ke tabel 3D (dinyatakan sebagai array 1D), yaitu semua kombinasi 3 huruf yang diizinkan. Dengan begitu Anda dapat menghilangkan lebih banyak kata dengan segera dan Anda mengurangi jumlah pencarian array untuk setiap kata sebanyak 1: Untuk 'halo', Anda hanya perlu 3 pencarian array: hel, ell, llo. Omong-omong, ini akan sangat cepat untuk membangun tabel ini, karena hanya ada 400 kemungkinan gerakan 3 huruf di kisi Anda.
Pra-hitung indeks gerakan di kisi Anda yang perlu Anda sertakan dalam tabel Anda. Untuk contoh di atas, Anda perlu mengatur entri berikut ke 'Benar':
(0,0) (0,1) -> here: h, b : [6][0]
(0,0) (1,0) -> here: h, e : [6][3]
(0,0) (1,1) -> here: h, e : [6][3]
(0,1) (0,0) -> here: b, h : [0][6]
(0,1) (0,2) -> here: b, c : [0][1]
.
:
- Juga mewakili kisi permainan Anda dalam larik 1-D dengan 16 entri dan memiliki tabel 3. yang dihitung sebelumnya berisi indeks ke dalam larik ini.
Saya yakin jika Anda menggunakan pendekatan ini, Anda bisa membuat kode Anda berjalan sangat cepat, jika kamus Anda sudah dihitung sebelumnya dan sudah dimuat ke dalam memori.
BTW: Hal lain yang menyenangkan untuk dilakukan, jika Anda membangun game, adalah menjalankan hal-hal semacam ini segera di latar belakang. Mulai membuat dan menyelesaikan gim pertama saat pengguna masih melihat layar judul di aplikasi Anda dan memasukkan jari ke posisi untuk menekan "Mainkan". Kemudian hasilkan dan selesaikan game berikutnya saat pengguna memainkan yang sebelumnya. Itu akan memberi Anda banyak waktu untuk menjalankan kode Anda.
(Saya suka masalah ini, jadi saya mungkin akan tergoda untuk mengimplementasikan proposal saya di Jawa suatu hari nanti untuk melihat bagaimana itu benar-benar melakukan ... Saya akan memposting kode di sini setelah saya melakukannya.)
MEMPERBARUI:
Oke, saya punya waktu hari ini dan mengimplementasikan ide ini di Jawa:
class DictionaryEntry {
public int[] letters;
public int[] triplets;
}
class BoggleSolver {
// Constants
final int ALPHABET_SIZE = 5; // up to 2^5 = 32 letters
final int BOARD_SIZE = 4; // 4x4 board
final int[] moves = {-BOARD_SIZE-1, -BOARD_SIZE, -BOARD_SIZE+1,
-1, +1,
+BOARD_SIZE-1, +BOARD_SIZE, +BOARD_SIZE+1};
// Technically constant (calculated here for flexibility, but should be fixed)
DictionaryEntry[] dictionary; // Processed word list
int maxWordLength = 0;
int[] boardTripletIndices; // List of all 3-letter moves in board coordinates
DictionaryEntry[] buildDictionary(String fileName) throws IOException {
BufferedReader fileReader = new BufferedReader(new FileReader(fileName));
String word = fileReader.readLine();
ArrayList<DictionaryEntry> result = new ArrayList<DictionaryEntry>();
while (word!=null) {
if (word.length()>=3) {
word = word.toUpperCase();
if (word.length()>maxWordLength) maxWordLength = word.length();
DictionaryEntry entry = new DictionaryEntry();
entry.letters = new int[word.length() ];
entry.triplets = new int[word.length()-2];
int i=0;
for (char letter: word.toCharArray()) {
entry.letters[i] = (byte) letter - 65; // Convert ASCII to 0..25
if (i>=2)
entry.triplets[i-2] = (((entry.letters[i-2] << ALPHABET_SIZE) +
entry.letters[i-1]) << ALPHABET_SIZE) +
entry.letters[i];
i++;
}
result.add(entry);
}
word = fileReader.readLine();
}
return result.toArray(new DictionaryEntry[result.size()]);
}
boolean isWrap(int a, int b) { // Checks if move a->b wraps board edge (like 3->4)
return Math.abs(a%BOARD_SIZE-b%BOARD_SIZE)>1;
}
int[] buildTripletIndices() {
ArrayList<Integer> result = new ArrayList<Integer>();
for (int a=0; a<BOARD_SIZE*BOARD_SIZE; a++)
for (int bm: moves) {
int b=a+bm;
if ((b>=0) && (b<board.length) && !isWrap(a, b))
for (int cm: moves) {
int c=b+cm;
if ((c>=0) && (c<board.length) && (c!=a) && !isWrap(b, c)) {
result.add(a);
result.add(b);
result.add(c);
}
}
}
int[] result2 = new int[result.size()];
int i=0;
for (Integer r: result) result2[i++] = r;
return result2;
}
// Variables that depend on the actual game layout
int[] board = new int[BOARD_SIZE*BOARD_SIZE]; // Letters in board
boolean[] possibleTriplets = new boolean[1 << (ALPHABET_SIZE*3)];
DictionaryEntry[] candidateWords;
int candidateCount;
int[] usedBoardPositions;
DictionaryEntry[] foundWords;
int foundCount;
void initializeBoard(String[] letters) {
for (int row=0; row<BOARD_SIZE; row++)
for (int col=0; col<BOARD_SIZE; col++)
board[row*BOARD_SIZE + col] = (byte) letters[row].charAt(col) - 65;
}
void setPossibleTriplets() {
Arrays.fill(possibleTriplets, false); // Reset list
int i=0;
while (i<boardTripletIndices.length) {
int triplet = (((board[boardTripletIndices[i++]] << ALPHABET_SIZE) +
board[boardTripletIndices[i++]]) << ALPHABET_SIZE) +
board[boardTripletIndices[i++]];
possibleTriplets[triplet] = true;
}
}
void checkWordTriplets() {
candidateCount = 0;
for (DictionaryEntry entry: dictionary) {
boolean ok = true;
int len = entry.triplets.length;
for (int t=0; (t<len) && ok; t++)
ok = possibleTriplets[entry.triplets[t]];
if (ok) candidateWords[candidateCount++] = entry;
}
}
void checkWords() { // Can probably be optimized a lot
foundCount = 0;
for (int i=0; i<candidateCount; i++) {
DictionaryEntry candidate = candidateWords[i];
for (int j=0; j<board.length; j++)
if (board[j]==candidate.letters[0]) {
usedBoardPositions[0] = j;
if (checkNextLetters(candidate, 1, j)) {
foundWords[foundCount++] = candidate;
break;
}
}
}
}
boolean checkNextLetters(DictionaryEntry candidate, int letter, int pos) {
if (letter==candidate.letters.length) return true;
int match = candidate.letters[letter];
for (int move: moves) {
int next=pos+move;
if ((next>=0) && (next<board.length) && (board[next]==match) && !isWrap(pos, next)) {
boolean ok = true;
for (int i=0; (i<letter) && ok; i++)
ok = usedBoardPositions[i]!=next;
if (ok) {
usedBoardPositions[letter] = next;
if (checkNextLetters(candidate, letter+1, next)) return true;
}
}
}
return false;
}
// Just some helper functions
String formatTime(long start, long end, long repetitions) {
long time = (end-start)/repetitions;
return time/1000000 + "." + (time/100000) % 10 + "" + (time/10000) % 10 + "ms";
}
String getWord(DictionaryEntry entry) {
char[] result = new char[entry.letters.length];
int i=0;
for (int letter: entry.letters)
result[i++] = (char) (letter+97);
return new String(result);
}
void run() throws IOException {
long start = System.nanoTime();
// The following can be pre-computed and should be replaced by constants
dictionary = buildDictionary("C:/TWL06.txt");
boardTripletIndices = buildTripletIndices();
long precomputed = System.nanoTime();
// The following only needs to run once at the beginning of the program
candidateWords = new DictionaryEntry[dictionary.length]; // WAAAY too generous
foundWords = new DictionaryEntry[dictionary.length]; // WAAAY too generous
usedBoardPositions = new int[maxWordLength];
long initialized = System.nanoTime();
for (int n=1; n<=100; n++) {
// The following needs to run again for every new board
initializeBoard(new String[] {"DGHI",
"KLPS",
"YEUT",
"EORN"});
setPossibleTriplets();
checkWordTriplets();
checkWords();
}
long solved = System.nanoTime();
// Print out result and statistics
System.out.println("Precomputation finished in " + formatTime(start, precomputed, 1)+":");
System.out.println(" Words in the dictionary: "+dictionary.length);
System.out.println(" Longest word: "+maxWordLength+" letters");
System.out.println(" Number of triplet-moves: "+boardTripletIndices.length/3);
System.out.println();
System.out.println("Initialization finished in " + formatTime(precomputed, initialized, 1));
System.out.println();
System.out.println("Board solved in "+formatTime(initialized, solved, 100)+":");
System.out.println(" Number of candidates: "+candidateCount);
System.out.println(" Number of actual words: "+foundCount);
System.out.println();
System.out.println("Words found:");
int w=0;
System.out.print(" ");
for (int i=0; i<foundCount; i++) {
System.out.print(getWord(foundWords[i]));
w++;
if (w==10) {
w=0;
System.out.println(); System.out.print(" ");
} else
if (i<foundCount-1) System.out.print(", ");
}
System.out.println();
}
public static void main(String[] args) throws IOException {
new BoggleSolver().run();
}
}
Berikut ini beberapa hasilnya:
Untuk kisi-kisi dari gambar yang diposting dalam pertanyaan asli (DGHI ...):
Precomputation finished in 239.59ms:
Words in the dictionary: 178590
Longest word: 15 letters
Number of triplet-moves: 408
Initialization finished in 0.22ms
Board solved in 3.70ms:
Number of candidates: 230
Number of actual words: 163
Words found:
eek, eel, eely, eld, elhi, elk, ern, erupt, erupts, euro
eye, eyer, ghi, ghis, glee, gley, glue, gluer, gluey, glut
gluts, hip, hiply, hips, his, hist, kelp, kelps, kep, kepi
kepis, keps, kept, kern, key, kye, lee, lek, lept, leu
ley, lunt, lunts, lure, lush, lust, lustre, lye, nus, nut
nuts, ore, ort, orts, ouph, ouphs, our, oust, out, outre
outs, oyer, pee, per, pert, phi, phis, pis, pish, plus
plush, ply, plyer, psi, pst, pul, pule, puler, pun, punt
punts, pur, pure, puree, purely, pus, push, put, puts, ree
rely, rep, reply, reps, roe, roue, roup, roups, roust, rout
routs, rue, rule, ruly, run, runt, runts, rupee, rush, rust
rut, ruts, ship, shlep, sip, sipe, spue, spun, spur, spurn
spurt, strep, stroy, stun, stupe, sue, suer, sulk, sulker, sulky
sun, sup, supe, super, sure, surely, tree, trek, trey, troupe
troy, true, truly, tule, tun, tup, tups, turn, tush, ups
urn, uts, yeld, yelk, yelp, yelps, yep, yeps, yore, you
your, yourn, yous
Untuk surat-surat yang diposting sebagai contoh dalam pertanyaan asli (FXIE ...)
Precomputation finished in 239.68ms:
Words in the dictionary: 178590
Longest word: 15 letters
Number of triplet-moves: 408
Initialization finished in 0.21ms
Board solved in 3.69ms:
Number of candidates: 87
Number of actual words: 76
Words found:
amble, ambo, ami, amie, asea, awa, awe, awes, awl, axil
axile, axle, boil, bole, box, but, buts, east, elm, emboli
fame, fames, fax, lei, lie, lima, limb, limbo, limbs, lime
limes, lob, lobs, lox, mae, maes, maw, maws, max, maxi
mesa, mew, mewl, mews, mil, mile, milo, mix, oil, ole
sae, saw, sea, seam, semi, sew, stub, swam, swami, tub
tubs, tux, twa, twae, twaes, twas, uts, wae, waes, wamble
wame, wames, was, wast, wax, west
Untuk 5x5-grid berikut:
R P R I T
A H H L N
I E T E P
Z R Y S G
O G W E Y
ini memberikan ini:
Precomputation finished in 240.39ms:
Words in the dictionary: 178590
Longest word: 15 letters
Number of triplet-moves: 768
Initialization finished in 0.23ms
Board solved in 3.85ms:
Number of candidates: 331
Number of actual words: 240
Words found:
aero, aery, ahi, air, airt, airth, airts, airy, ear, egest
elhi, elint, erg, ergo, ester, eth, ether, eye, eyen, eyer
eyes, eyre, eyrie, gel, gelt, gelts, gen, gent, gentil, gest
geste, get, gets, gey, gor, gore, gory, grey, greyest, greys
gyre, gyri, gyro, hae, haet, haets, hair, hairy, hap, harp
heap, hear, heh, heir, help, helps, hen, hent, hep, her
hero, hes, hest, het, hetero, heth, hets, hey, hie, hilt
hilts, hin, hint, hire, hit, inlet, inlets, ire, leg, leges
legs, lehr, lent, les, lest, let, lethe, lets, ley, leys
lin, line, lines, liney, lint, lit, neg, negs, nest, nester
net, nether, nets, nil, nit, ogre, ore, orgy, ort, orts
pah, pair, par, peg, pegs, peh, pelt, pelter, peltry, pelts
pen, pent, pes, pest, pester, pesty, pet, peter, pets, phi
philter, philtre, phiz, pht, print, pst, rah, rai, rap, raphe
raphes, reap, rear, rei, ret, rete, rets, rhaphe, rhaphes, rhea
ria, rile, riles, riley, rin, rye, ryes, seg, sel, sen
sent, senti, set, sew, spelt, spelter, spent, splent, spline, splint
split, stent, step, stey, stria, striae, sty, stye, tea, tear
teg, tegs, tel, ten, tent, thae, the, their, then, these
thesp, they, thin, thine, thir, thirl, til, tile, tiles, tilt
tilter, tilth, tilts, tin, tine, tines, tirl, trey, treys, trog
try, tye, tyer, tyes, tyre, tyro, west, wester, wry, wryest
wye, wyes, wyte, wytes, yea, yeah, year, yeh, yelp, yelps
yen, yep, yeps, yes, yester, yet, yew, yews, zero, zori
Untuk ini saya menggunakan TWL06 Tournament Scrabble Word List , karena tautan dalam pertanyaan awal tidak lagi berfungsi. File ini 1.85MB, jadi sedikit lebih pendek. Dan buildDictionary
fungsinya membuang semua kata dengan kurang dari 3 huruf.
Berikut adalah beberapa pengamatan tentang kinerja ini:
Ini sekitar 10 kali lebih lambat dari kinerja OCaml Victor Nicollet yang dilaporkan. Apakah ini disebabkan oleh algoritma yang berbeda, kamus pendek yang digunakannya, fakta bahwa kodenya dikompilasi dan milikku berjalan di mesin virtual Java, atau kinerja komputer kita (milikku adalah Intel Q6600 @ 2.4MHz yang menjalankan WinXP), Saya tidak tahu Tapi ini jauh lebih cepat daripada hasil untuk implementasi lain yang dikutip di akhir pertanyaan awal. Jadi, apakah algoritma ini lebih baik daripada kamus trie atau tidak, saya tidak tahu pada saat ini.
Metode tabel yang digunakan dalam checkWordTriplets()
menghasilkan perkiraan yang sangat baik untuk jawaban yang sebenarnya. Hanya 1 dari 3-5 kata yang dilewati akan gagal dalam checkWords()
ujian (Lihat jumlah kandidat vs. jumlah kata aktual di atas)
Sesuatu yang tidak dapat Anda lihat di atas: checkWordTriplets()
Fungsi ini membutuhkan waktu sekitar 3,65 ms dan karenanya sepenuhnya dominan dalam proses pencarian. The checkWords()
Fungsi memakan cukup banyak 0,05-0,20 ms tersisa.
Waktu pelaksanaan checkWordTriplets()
fungsi tergantung secara linear pada ukuran kamus dan hampir tidak tergantung pada ukuran papan!
Waktu pelaksanaan checkWords()
tergantung pada ukuran papan dan jumlah kata yang tidak dikesampingkan oleh checkWordTriplets()
.
The checkWords()
implementasi di atas adalah paling bodoh versi pertama saya datang dengan. Ini pada dasarnya tidak dioptimalkan sama sekali. Tetapi dibandingkan dengan checkWordTriplets()
itu tidak relevan untuk kinerja total aplikasi, jadi saya tidak khawatir tentang hal itu. Tetapi , jika ukuran papan semakin besar, fungsi ini akan semakin lambat dan lambat dan pada akhirnya akan mulai berarti. Maka, itu perlu dioptimalkan juga.
Satu hal yang menyenangkan tentang kode ini adalah fleksibilitasnya:
- Anda dapat dengan mudah mengubah ukuran papan: Perbarui baris 10 dan array String diteruskan ke
initializeBoard()
.
- Ini dapat mendukung huruf yang lebih besar / berbeda dan dapat menangani hal-hal seperti memperlakukan 'Qu' sebagai satu huruf tanpa ada overhead kinerja. Untuk melakukan ini, orang perlu memperbarui baris 9 dan beberapa tempat di mana karakter dikonversi ke angka (saat ini hanya dengan mengurangi 65 dari nilai ASCII)
Ok, tapi saya pikir sekarang posting ini sudah cukup lama. Saya pasti bisa menjawab pertanyaan yang mungkin Anda miliki, tapi mari kita pindah ke komentar.