ฉันคิดว่าคุณอาจจะใช้เวลาส่วนใหญ่พยายามจับคู่คำที่ไม่สามารถสร้างโดยตารางจดหมายของคุณ ดังนั้นสิ่งแรกที่ฉันจะทำคือพยายามเร่งความเร็วในขั้นตอนนั้นและคุณจะได้รับประโยชน์สูงสุดจากที่นั่น
สำหรับเรื่องนี้ฉันจะแสดงตารางเป็นตาราง "การเคลื่อนไหว" ที่เป็นไปได้ที่คุณทำดัชนีโดยการเปลี่ยนแปลงตัวอักษรที่คุณกำลังดู
เริ่มต้นด้วยการกำหนดตัวเลขแต่ละตัวจากตัวอักษรทั้งหมดของคุณ (A = 0, B = 1, C = 2, ... และอื่น ๆ )
ลองตัวอย่างนี้:
h b c d
e e g h
l l k l
m o f p
และสำหรับตอนนี้ให้ใช้ตัวอักษรของตัวอักษรที่เรามี (โดยปกติแล้วคุณอาจต้องการใช้ตัวอักษรทั้งหมดเหมือนกันทุกครั้ง):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
จากนั้นคุณสร้างอาร์เรย์บูลีน 2D ที่บอกว่าคุณมีการเปลี่ยนตัวอักษรที่แน่นอนหรือไม่:
| 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
ตอนนี้ไปที่รายการคำศัพท์ของคุณและแปลงคำเป็นช่วงการเปลี่ยนภาพ:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
จากนั้นตรวจสอบว่าช่วงการเปลี่ยนภาพเหล่านี้ได้รับอนุญาตหรือไม่โดยค้นหาในตารางของคุณ:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
หากพวกเขาได้รับอนุญาตทั้งหมดมีโอกาสที่อาจพบคำนี้
ตัวอย่างเช่นคำว่า "หมวกกันน็อก" สามารถตัดออกในช่วงการเปลี่ยนภาพครั้งที่ 4 (m ถึง e: helMEt) เนื่องจากรายการในตารางของคุณเป็นเท็จ
และสามารถตัดคำแฮมสเตอร์ออกได้เนื่องจากไม่อนุญาตการเปลี่ยน (h ถึง a) ครั้งแรก (ไม่มีอยู่ในตารางของคุณ)
ทีนี้สำหรับคำที่เหลืออยู่ที่คุณไม่ได้กำจัดให้ลองค้นหาคำเหล่านั้นในตารางตามที่คุณทำตอนนี้หรือตามคำแนะนำในคำตอบอื่น ๆ ที่นี่ เพื่อหลีกเลี่ยงผลบวกปลอมที่เกิดจากการกระโดดข้ามระหว่างตัวอักษรที่เหมือนกันในกริดของคุณ ตัวอย่างเช่นคำว่า "ความช่วยเหลือ" ได้รับอนุญาตจากตาราง แต่ไม่ใช่โดยกริด
เคล็ดลับการปรับปรุงประสิทธิภาพเพิ่มเติมเกี่ยวกับแนวคิดนี้:
แทนที่จะใช้อาร์เรย์ 2D ให้ใช้อาร์เรย์ 1D และคำนวณดัชนีของตัวอักษรตัวที่สองด้วยตัวคุณเอง ดังนั้นแทนที่จะเป็นอาร์เรย์ 12x12 เช่นด้านบนให้สร้างอาร์เรย์ 1D ที่มีความยาว 144 หากคุณใช้ตัวอักษรเดียวกันเสมอ (เช่น 26x26 = 676x1 อาร์เรย์สำหรับตัวอักษรภาษาอังกฤษมาตรฐาน) แม้ว่าจะไม่มีตัวอักษรทั้งหมดปรากฏในตารางของคุณ คุณสามารถคำนวณดัชนีล่วงหน้าลงในอาร์เรย์ 1D ที่คุณต้องการทดสอบเพื่อให้ตรงกับคำในพจนานุกรมของคุณ ตัวอย่างเช่นดัชนีสำหรับ 'สวัสดี' ในตัวอย่างด้านบนจะเป็น
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
ขยายแนวคิดไปยังตาราง 3D (แสดงเป็นอาร์เรย์ 1D) นั่นคือทั้งหมดที่อนุญาตให้รวมกัน 3 ตัวอักษร ด้วยวิธีนี้คุณสามารถกำจัดคำได้มากขึ้นในทันทีและลดจำนวนการค้นหาอาร์เรย์สำหรับแต่ละคำด้วย 1: สำหรับ 'hello' คุณจะต้องค้นหาอาร์เรย์ 3 ครั้งเท่านั้น: hel, ell, llo มันจะเร็วมากในการสร้างตารางนี้โดยวิธีการเนื่องจากมีเพียง 3 ตัวอักษรที่เป็นไปได้ 400 ตัวในตารางของคุณ
คำนวณดัชนีการเคลื่อนไหวล่วงหน้าในตารางของคุณที่คุณต้องการรวมไว้ในตารางของคุณ สำหรับตัวอย่างข้างต้นคุณต้องตั้งค่ารายการต่อไปนี้เป็น 'จริง':
(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]
.
:
- นอกจากนี้ยังแสดงตารางเกมของคุณในอาร์เรย์ 1 มิติที่มี 16 รายการและมีตารางคำนวณล่วงหน้าใน 3 มีดัชนีในอาร์เรย์นี้
ฉันแน่ใจว่าถ้าคุณใช้วิธีนี้คุณจะสามารถทำให้โค้ดของคุณทำงานได้อย่างรวดเร็วเมามันถ้าคุณมีพจนานุกรมที่คำนวณล่วงหน้าและโหลดไปยังหน่วยความจำแล้ว
BTW: สิ่งที่ดีอีกอย่างที่ต้องทำถ้าคุณกำลังสร้างเกมคือการใช้สิ่งต่าง ๆ เหล่านี้ทันทีในพื้นหลัง เริ่มสร้างและแก้ไขเกมแรกในขณะที่ผู้ใช้ยังคงดูหน้าจอชื่อเรื่องในแอพของคุณและให้นิ้วของเขาเข้าสู่ตำแหน่งเพื่อกด "เล่น" จากนั้นสร้างและแก้ไขเกมถัดไปเมื่อผู้ใช้เล่นก่อนหน้านี้ ซึ่งควรให้เวลากับการรันโค้ดเป็นจำนวนมาก
(ฉันชอบปัญหานี้ดังนั้นฉันอาจถูกล่อลวงให้นำข้อเสนอของฉันไปใช้กับ Java ในบางวันในวันถัดไปเพื่อดูว่าจริงแล้วมันจะทำงานอย่างไร ... ฉันจะโพสต์โค้ดที่นี่เมื่อฉันทำ)
UPDATE:
ตกลงฉันมีเวลาวันนี้และนำแนวคิดนี้ไปใช้ใน Java:
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();
}
}
นี่คือผลลัพธ์บางส่วน:
สำหรับตารางจากภาพที่โพสต์ในคำถามดั้งเดิม (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
สำหรับตัวอักษรที่โพสต์เป็นตัวอย่างในคำถามเดิม (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
สำหรับ 5x5-grid ต่อไปนี้:
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
มันให้สิ่งนี้:
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
สำหรับสิ่งนี้ฉันใช้TWL06 Tournament Scrabble Word Listเนื่องจากลิงก์ในคำถามเดิมไม่ทำงานอีกต่อไป ไฟล์นี้มีขนาด 1.85MB ดังนั้นจึงสั้นกว่านี้เล็กน้อย และbuildDictionary
ฟังก์ชั่นการส่งคำทั้งหมดมีน้อยกว่า 3 ตัวอักษร
นี่คือข้อสังเกตสองประการเกี่ยวกับประสิทธิภาพของสิ่งนี้:
ช้ากว่าการรายงานการใช้ OCaml ของ Victor Nicollet ประมาณ 10 เท่า ไม่ว่าจะเกิดจากอัลกอริทึมที่ต่างกัน, พจนานุกรมสั้น ๆ ที่เขาใช้, ความจริงที่ว่าโค้ดของเขาถูกคอมไพล์และการรันของฉันในเครื่องเสมือน Java หรือประสิทธิภาพของคอมพิวเตอร์ของเรา (ของฉันคือ Intel Q6600 @ 2.4MHz ฉันไม่รู้ แต่เร็วกว่าผลลัพธ์สำหรับการใช้งานอื่น ๆ ที่ยกมาตอนท้ายของคำถามเดิม ดังนั้นไม่ว่าอัลกอริทึมนี้จะเหนือกว่าพจนานุกรม Trie หรือไม่ฉันไม่รู้ตอนนี้
วิธีตารางที่ใช้ในcheckWordTriplets()
การประมาณค่าที่ดีมากกับคำตอบที่เกิดขึ้นจริง มีเพียง 1 ใน 3-5 คำที่ผ่านไปเท่านั้นที่จะทำให้การcheckWords()
ทดสอบล้มเหลว(ดูจำนวนผู้สมัครและจำนวนคำที่แท้จริงด้านบน)
สิ่งที่คุณไม่สามารถมองเห็นด้านบน: checkWordTriplets()
ฟังก์ชั่นนี้ใช้เวลาประมาณ 3.65ms และโดดเด่นอย่างสมบูรณ์ในกระบวนการค้นหา checkWords()
ฟังก์ชั่นใช้เวลาถึงสวยมากเหลือ 0.05-0.20 มิลลิวินาที
เวลาดำเนินการของcheckWordTriplets()
ฟังก์ชั่นขึ้นอยู่กับขนาดพจนานุกรมเป็นเส้นตรงและไม่ขึ้นกับขนาดกระดาน!
เวลาการดำเนินการของขึ้นอยู่กับขนาดคณะกรรมการและจำนวนคำไม่ได้ปกครองออกโดยcheckWords()
checkWordTriplets()
การcheckWords()
ใช้งานด้านบนเป็นเวอร์ชั่นแรกที่น่าเบื่อที่สุดที่ฉันคิดไว้ มันไม่ได้ปรับให้เหมาะที่สุดเลย แต่เมื่อเทียบกับcheckWordTriplets()
มันไม่เกี่ยวข้องกับประสิทธิภาพโดยรวมของแอปพลิเคชันดังนั้นฉันจึงไม่ต้องกังวลกับมัน แต่ถ้าขนาดบอร์ดใหญ่ขึ้นฟังก์ชั่นนี้จะช้าลงและช้าลงและในที่สุดก็จะเริ่มมีความหมาย จากนั้นก็จะต้องมีการปรับให้เหมาะสมเช่นกัน
สิ่งหนึ่งที่ดีเกี่ยวกับรหัสนี้คือความยืดหยุ่น:
- คุณสามารถเปลี่ยนขนาดบอร์ดได้อย่างง่ายดาย: อัปเดตบรรทัดที่ 10 และอาร์เรย์สตริงที่ส่งไปยัง
initializeBoard()
และอาเรย์สตริงที่ผ่านมา
- มันสามารถรองรับตัวอักษรขนาดใหญ่ / แตกต่างกันและสามารถจัดการสิ่งต่าง ๆ เช่นการรักษา 'Qu' เป็นตัวอักษรเดียวโดยไม่มีค่าใช้จ่ายในการแสดง ในการทำเช่นนี้เราจำเป็นต้องอัปเดตบรรทัดที่ 9 และสถานที่สองแห่งที่แปลงอักขระเป็นตัวเลข (ปัจจุบันเพียงลบ 65 จากค่า ASCII)
ตกลง แต่ฉันคิดว่าตอนนี้โพสต์นี้จะอยู่นานพอ แน่นอนฉันสามารถตอบคำถามใด ๆ ที่คุณอาจมี แต่ขอย้ายไปที่ความคิดเห็น