Sanırım muhtemelen zamanınızın çoğunu harf kılavuzunuz tarafından oluşturulamayacak kelimelerle eşleştirmeye çalışacaksınız. Yani, ilk yapacağım şey bu adımı hızlandırmaya çalışmak ve bu sizi oraya en çok götürecek.
Bunun için ızgarayı, baktığınız harf geçişiyle indekslediğiniz olası "hareketlerin" tablosu olarak yeniden ifade edeceğim.
Her harfe tüm alfabenizden bir sayı atayarak başlayın (A = 0, B = 1, C = 2, ... vb.).
Şimdi bu örneği ele alalım:
h b c d
e e g h
l l k l
m o f p
Şimdilik, sahip olduğumuz harflerin alfabesini kullanalım (genellikle her seferinde aynı tüm alfabeyi kullanmak istersiniz):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Ardından, belirli bir harf geçişinizin olup olmadığını söyleyen bir 2D boolean dizisi yaparsınız:
| 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
Şimdi kelime listenizi inceleyin ve kelimeleri geçişlere dönüştürün:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Ardından, tablolarınıza bakarak bu geçişlere izin verilip verilmediğini kontrol edin:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Hepsine izin verilirse, bu kelimenin bulunma ihtimali vardır.
Örneğin tablonuzdaki bu giriş yanlış olduğundan "kask" kelimesi 4. geçişte (m'den e: helMEt'e) göz ardı edilebilir.
Ve ilk (h'den a'ya) geçişe izin verilmediğinden (tablonuzda bile mevcut değil) ve hamster kelimesi dışlanabilir.
Şimdi, muhtemelen ortadan kaldırmamanız gereken çok az kelime için, bunları şimdi yaptığınız gibi veya buradaki diğer cevapların bazılarında önerildiği gibi ızgarada bulmaya çalışın. Bu, ızgaranızdaki aynı harfler arasındaki sıçramalardan kaynaklanan yanlış pozitifleri önlemek içindir. Örneğin, "yardım" kelimesine tablo tarafından izin verilir, ancak ızgara tarafından izin verilmez.
Bu fikirle ilgili bazı performans iyileştirme ipuçları:
Bir 2D dizisi kullanmak yerine, 1D dizisi kullanın ve ikinci harfin dizinini kendiniz hesaplayın. Yani, yukarıdaki gibi 12x12 bir dizi yerine, 144 uzunluğunda bir 1D dizisi yapın. Daha sonra her zaman aynı alfabeyi (yani standart İngilizce alfabe için bir 26x26 = 676x1 dizisi) kullanırsanız, ızgarada tüm harfler görünmese bile , endeksleri sözlük kelimelerinizle eşleşmesi için test etmeniz gereken bu 1D dizisine önceden hesaplayabilirsiniz. Örneğin, yukarıdaki örnekte 'merhaba' endeksleri
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
Fikri 3B tabloya (1D dizisi olarak ifade edilir), yani izin verilen tüm 3 harfli kombinasyonlara uzatın. Bu şekilde daha fazla kelimeyi hemen ortadan kaldırabilir ve her kelime için dizi arama sayısını 1 azaltabilirsiniz: 'Merhaba' için sadece 3 dizi aramasına ihtiyacınız vardır: hel, ell, llo. Bu tabloyu oluşturmak çok hızlı olacak, çünkü ızgarada sadece 400 olası 3 harfli hareket var.
Izgaradaki tablonuza eklemeniz gereken hareketlerin indekslerini önceden hesaplayın. Yukarıdaki örnek için aşağıdaki girişleri 'Doğru' olarak ayarlamanız gerekir:
(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]
.
:
- Ayrıca, oyun kılavuzunuzu 16 girişli bir 1-D dizide temsil edin ve tablo 3'te önceden hesaplanmış olarak dizinleri bu diziye dahil edin.
Eminim bu yaklaşımı kullanırsanız, önceden hesaplanmış ve zaten belleğe yüklenmişse, kodunuzu inanılmaz hızlı çalıştırabilirsiniz.
BTW: Yapacak başka bir güzel şey, eğer bir oyun inşa ediyorsanız, bu tür şeyleri hemen arka planda çalıştırmaktır. Kullanıcı hala uygulamanızdaki başlık ekranına bakarken ve "Oynat" a basmak için parmağını yerine getirirken ilk oyunu oluşturmaya ve çözmeye başlayın. Ardından kullanıcı bir öncekini oynadıkça bir sonraki oyunu oluşturun ve çözün. Bu, kodunuzu çalıştırmak için size çok zaman vermelidir.
(Bu sorunu seviyorum, bu yüzden muhtemelen nasıl bir performans sergilediğini görmek için önümüzdeki günlerde Java teklifimi uygulamak için cazip olacak ... Ben bir kez kod burada göndereceğiz.)
GÜNCELLEME:
Tamam, bugün biraz zaman geçirdim ve bu fikri Java'da uyguladım:
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();
}
}
İşte bazı sonuçlar:
Orijinal sorudaki (DGHI ...) yayınlanan resimdeki ızgara için:
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
Orijinal soruda (FXIE ...) örnek olarak gönderilen mektuplar için
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
Aşağıdaki 5x5 ızgara için:
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
bunu verir:
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
Bunun için TWL06 Turnuvası Scrabble Kelime Listesini kullandım , çünkü orijinal sorudaki bağlantı artık çalışmıyor. Bu dosya 1.85MB, bu yüzden biraz daha kısa. Ve buildDictionary
fonksiyon 3 kelimeden az olan tüm kelimeleri atar.
İşte bunun performansı hakkında birkaç gözlem:
Victor Nicollet'in OCaml uygulamasının rapor edilen performansından yaklaşık 10 kat daha yavaş. Bunun nedeni farklı algoritma, daha kısa sözlük olması, kodunun derlenmesi ve benim bir Java sanal makinesinde çalışması ya da bilgisayarlarımızın performansı (benimki WinXP çalıştıran Intel Q6600 @ 2.4MHz'dir), Bilmiyorum. Ancak orijinal sorunun sonunda alıntılanan diğer uygulamaların sonuçlarından çok daha hızlıdır. Yani, bu algoritma trie sözlüğünden daha üstün olsun ya da olmasın, bu noktada bilmiyorum.
Kullanılan tablo yöntemi checkWordTriplets()
, gerçek cevaplara çok iyi bir yaklaşım vermektedir. Sadece 3-05 Ocak kelimeler başarısız olur geçti checkWords()
testi (Bkz adayların sayısını vs fiili sözcük sayısı üzerinde).
Yukarıda göremediğiniz bir şey: checkWordTriplets()
İşlev yaklaşık 3,65 ms sürer ve bu nedenle arama sürecinde tamamen baskındır. checkWords()
Fonksiyon hemen hemen kalan 0,05-0,20 ms kaplıyor.
checkWordTriplets()
İşlevin yürütme süresi doğrusal olarak sözlük boyutuna bağlıdır ve neredeyse pano boyutundan bağımsızdır!
Uygulama süresi checkWords()
, pano boyutuna ve hariç bırakılmayan kelime sayısına bağlıdır checkWordTriplets()
.
Yukarıdaki checkWords()
uygulama ben geldi en aptal ilk sürümüdür. Temel olarak hiç optimize edilmemiştir. Ancak bununla karşılaştırıldığında checkWordTriplets()
uygulamanın toplam performansı için önemsizdir, bu yüzden endişelenmedim. Ancak , tahta boyutu büyürse, bu işlev yavaşlar ve yavaşlar ve sonunda önemli olmaya başlar. Ardından, optimize edilmesi de gerekir.
Bu kodla ilgili güzel bir şey esnekliğidir:
- Pano boyutunu kolayca değiştirebilirsiniz: Satır 10'u ve Dize dizisini güncelleyin
initializeBoard()
.
- Daha büyük / farklı alfabeleri destekleyebilir ve herhangi bir performans yükü olmadan 'Qu' harfini tek harf olarak işlemek gibi işlemleri yapabilir. Bunu yapmak için, satır 9'u ve karakterlerin sayılara dönüştürüldüğü birkaç yeri güncellemek gerekir (şu anda sadece ASCII değerinden 65 çıkarılarak)
Tamam, ama şimdi bu yazının yeterince uzun olduğunu düşünüyorum. Sorularınızı kesinlikle yanıtlayabilirim, ama bunu yorumlara taşıyalım.