Tôi nghĩ rằng bạn có thể sẽ dành phần lớn thời gian của mình để cố gắng ghép các từ mà lưới chữ cái của bạn không thể xây dựng được. Vì vậy, điều đầu tiên tôi sẽ làm là cố gắng tăng tốc bước đó và điều đó sẽ giúp bạn đi gần hết quãng đường đó.
Đối với điều này, tôi sẽ thể hiện lại lưới dưới dạng một bảng "di chuyển" có thể mà bạn lập chỉ mục bằng cách chuyển chữ cái mà bạn đang xem.
Bắt đầu bằng cách gán cho mỗi chữ cái một số từ toàn bộ bảng chữ cái của bạn (A = 0, B = 1, C = 2, ... và vv).
Hãy lấy ví dụ này:
h b c d
e e g h
l l k l
m o f p
Và bây giờ, hãy sử dụng bảng chữ cái của các chữ cái chúng ta có (thông thường bạn có thể muốn sử dụng cùng một bảng chữ cái mỗi lần):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Sau đó, bạn tạo một mảng boolean 2D cho biết bạn có sẵn một chuyển đổi chữ cái nào đó không:
| 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
Bây giờ đi qua danh sách từ của bạn và chuyển đổi các từ thành chuyển tiếp:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Sau đó kiểm tra xem các chuyển đổi này có được phép hay không bằng cách tra cứu chúng trong bảng của bạn:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Nếu tất cả chúng đều được cho phép, có khả năng từ này có thể được tìm thấy.
Ví dụ, từ "mũ bảo hiểm" có thể được loại trừ trong lần chuyển đổi thứ 4 (m sang e: helMEt), vì mục nhập trong bảng của bạn là sai.
Và từ hamster có thể được loại trừ, vì quá trình chuyển đổi (h sang a) đầu tiên không được phép (thậm chí không tồn tại trong bảng của bạn).
Bây giờ, đối với rất ít từ còn lại mà bạn không loại bỏ, hãy thử thực sự tìm thấy chúng trong lưới theo cách bạn đang làm bây giờ hoặc như được đề xuất trong một số câu trả lời khác ở đây. Điều này là để tránh các kết quả dương tính giả do nhảy giữa các chữ cái giống hệt nhau trong lưới của bạn. Ví dụ, từ "trợ giúp" được cho phép bởi bảng, nhưng không phải bởi lưới.
Một số mẹo cải thiện hiệu suất hơn nữa về ý tưởng này:
Thay vì sử dụng mảng 2D, hãy sử dụng mảng 1D và chỉ cần tự mình tính toán chỉ số của chữ cái thứ hai. Vì vậy, thay vì mảng 12x12 như trên, hãy tạo mảng 1D có độ dài 144. Nếu sau đó bạn luôn sử dụng cùng một bảng chữ cái (tức là mảng 26x26 = 676x1 cho bảng chữ cái tiếng Anh chuẩn), ngay cả khi không phải tất cả các chữ cái đều hiển thị trong lưới của bạn , bạn có thể tính toán trước các chỉ số vào mảng 1D này mà bạn cần kiểm tra để khớp với các từ trong từ điển của bạn. Ví dụ: các chỉ số cho 'xin chào' trong ví dụ trên sẽ là
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
Mở rộng ý tưởng sang bảng 3D (được biểu thị dưới dạng mảng 1D), tức là tất cả các kết hợp 3 chữ cái được phép. Bằng cách đó, bạn có thể loại bỏ nhiều từ hơn ngay lập tức và bạn giảm số lần tra cứu mảng cho mỗi từ bằng 1: Đối với 'xin chào', bạn chỉ cần 3 lần tra cứu mảng: hel, ell, llo. Nhân tiện, sẽ rất nhanh để xây dựng bảng này, vì chỉ có 400 chuyển động 3 chữ cái có thể có trong lưới của bạn.
Tính toán trước các chỉ số của các di chuyển trong lưới của bạn mà bạn cần đưa vào bảng của mình. Đối với ví dụ trên, bạn cần đặt các mục sau thành 'Đúng':
(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]
.
:
- Đồng thời biểu diễn lưới trò chơi của bạn trong mảng 1-D với 16 mục và có bảng được tính toán trước trong 3. chứa các chỉ mục vào mảng này.
Tôi chắc chắn nếu bạn sử dụng phương pháp này, bạn có thể khiến mã của mình chạy cực nhanh, nếu bạn có từ điển được tính toán trước và đã được tải vào bộ nhớ.
BTW: Một điều tuyệt vời khác để làm, nếu bạn đang xây dựng một trò chơi, là chạy những thứ này ngay lập tức trong nền. Bắt đầu tạo và giải quyết trò chơi đầu tiên trong khi người dùng vẫn đang nhìn vào màn hình tiêu đề trên ứng dụng của bạn và đưa ngón tay vào vị trí để nhấn "Chơi". Sau đó tạo và giải quyết trò chơi tiếp theo khi người dùng chơi trò chơi trước đó. Điều đó sẽ cho bạn rất nhiều thời gian để chạy mã của bạn.
(Tôi thích vấn đề này, vì vậy có lẽ tôi sẽ bị cám dỗ thực hiện đề xuất của mình trong Java vào một ngày nào đó để xem nó thực sự sẽ hoạt động như thế nào ... Tôi sẽ đăng mã ở đây sau khi tôi làm.)
CẬP NHẬT:
Ok, tôi đã có một chút thời gian hôm nay và thực hiện ý tưởng này trong 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();
}
}
Đây là một số kết quả:
Đối với lưới từ hình ảnh được đăng trong câu hỏi ban đầu (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
Đối với các chữ cái được đăng làm ví dụ trong câu hỏi ban đầu (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
Đối với lưới 5x5 sau:
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
nó cho cái nà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
Đối với điều này, tôi đã sử dụng Danh sách từ Scrabble giải đấu TWL06 , vì liên kết trong câu hỏi ban đầu không còn hoạt động. Tệp này là 1,85 MB, vì vậy nó ngắn hơn một chút. Và buildDictionary
hàm ném ra tất cả các từ có ít hơn 3 chữ cái.
Dưới đây là một vài quan sát về hiệu suất của việc này:
Nó chậm hơn khoảng 10 lần so với hiệu suất được báo cáo về việc thực hiện OCaml của Victor Nicollet. Cho dù điều này là do thuật toán khác nhau, từ điển ngắn hơn mà anh ta đã sử dụng, thực tế là mã của anh ta được biên dịch và mã của tôi chạy trong máy ảo Java hoặc hiệu suất của các máy tính của chúng tôi (của tôi là Intel Q6600 @ 2.4 MHz chạy WinXP), Tôi không biết. Nhưng nó nhanh hơn nhiều so với kết quả cho các triển khai khác được trích dẫn ở cuối câu hỏi ban đầu. Vì vậy, liệu thuật toán này có vượt trội hơn so với từ điển trie hay không, tôi không biết tại thời điểm này.
Phương pháp bảng được sử dụng checkWordTriplets()
mang lại một xấp xỉ rất tốt cho các câu trả lời thực tế. Chỉ 1 trong 3-5 từ được thông qua sẽ không vượt qua checkWords()
bài kiểm tra (Xem số lượng thí sinh so với số từ thực tế ở trên).
Một cái gì đó bạn không thể thấy ở trên: checkWordTriplets()
Hàm mất khoảng 3,65ms và do đó hoàn toàn chiếm ưu thế trong quá trình tìm kiếm. Các checkWords()
chức năng chiếm khá nhiều 0,05-0,20 ms còn lại.
Thời gian thực hiện của checkWordTriplets()
chức năng phụ thuộc tuyến tính vào kích thước từ điển và hầu như không phụ thuộc vào kích thước bảng!
Thời gian thực hiện checkWords()
phụ thuộc vào kích thước bảng và số lượng từ không được loại trừ bởi checkWordTriplets()
.
Việc checkWords()
thực hiện ở trên là phiên bản đầu tiên ngu ngốc nhất mà tôi nghĩ ra. Về cơ bản nó không được tối ưu hóa. Nhưng so với checkWordTriplets()
nó là không liên quan đến tổng hiệu suất của ứng dụng, vì vậy tôi không lo lắng về nó. Nhưng , nếu kích thước bảng trở nên lớn hơn, chức năng này sẽ ngày càng chậm hơn và cuối cùng sẽ bắt đầu quan trọng. Sau đó, nó sẽ cần phải được tối ưu hóa là tốt.
Một điều tốt đẹp về mã này là tính linh hoạt của nó:
- Bạn có thể dễ dàng thay đổi kích thước bảng: Cập nhật dòng 10 và mảng Chuỗi được truyền vào
initializeBoard()
.
- Nó có thể hỗ trợ các bảng chữ cái lớn hơn / khác nhau và có thể xử lý những thứ như coi 'Qu' là một chữ cái mà không có bất kỳ chi phí hiệu năng nào. Để làm điều này, người ta sẽ cần cập nhật dòng 9 và một vài vị trí nơi các ký tự được chuyển đổi thành số (hiện chỉ đơn giản bằng cách trừ 65 từ giá trị ASCII)
Ok, nhưng tôi nghĩ rằng bây giờ bài viết này là waaaay đủ lâu. Tôi chắc chắn có thể trả lời bất kỳ câu hỏi nào bạn có thể có, nhưng hãy chuyển câu hỏi đó sang các bình luận.