おそらく、あなたの時間の大部分は、文字グリッドでは構築できない可能性のある単語と一致させることに費やしていると思います。だから、私が最初にやろうとしていることは、そのステップをスピードアップすることです。
このため、私はグリッドを、見ている文字遷移によってインデックス付けされる可能な「移動」の表として再表現します。
まず、各文字にアルファベット全体の番号を割り当てます(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
それらがすべて許可されている場合、この単語が見つかる可能性があります。
たとえば、「helmet」という単語は、4番目の遷移(mからe:helMEt)で除外できます。これは、テーブルのエントリがfalseであるためです。
また、最初の(hからaへの)遷移が許可されていない(テーブルに存在していない)ため、ハムスターという単語を除外できます。
さて、あなたが排除しなかったおそらく非常に少数の残りの単語について、あなたが今それをしている方法で、またはここの他のいくつかの答えで示唆されているように、グリッドで実際にそれらを見つけてみてください。これは、グリッド内の同一の文字間のジャンプから生じる誤検知を回避するためです。たとえば、「ヘルプ」という単語はテーブルでは許可されていますが、グリッドでは許可されていません。
このアイデアに関するさらにいくつかのパフォーマンス改善のヒント:
2D配列を使用する代わりに、1D配列を使用して、2番目の文字のインデックスを自分で計算します。したがって、上記のような12x12配列の代わりに、長さ144の1D配列を作成します。すべての文字がグリッドに表示されなくても、常に同じアルファベット(つまり、標準の英語のアルファベットには26x26 = 676x1配列)を使用する場合、インデックスをこの1D配列に事前計算して、辞書の単語と一致するかどうかをテストする必要があります。たとえば、上の例の 'hello'のインデックスは次のようになります。
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つ減らすことができます。ちなみに、グリッドには3文字の移動が400回しかないため、このテーブルを作成するのは非常に迅速です。
テーブルに含める必要のあるグリッド内の動きのインデックスを事前に計算します。上記の例では、次のエントリを「True」に設定する必要があります。
(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]
.
:
- また、ゲームグリッドを16エントリの1次元配列で表し、3で事前計算されたテーブルを作成します。この配列へのインデックスが含まれます。
この方法を使用すると、辞書が事前に計算され、メモリに既に読み込まれている場合、コードを非常に高速に実行できます。
ところで、ゲームを構築している場合に行うもう1つの良いことは、このようなことをバックグラウンドですぐに実行することです。ユーザーがアプリのタイトル画面を見ている間に、最初のゲームの生成と解決を開始し、指で「再生」を押す位置に移動します。次に、ユーザーが前のゲームをプレイするときに、次のゲームを生成して解決します。これにより、コードを実行するのに多くの時間が与えられます。
(私はこの問題が好きなので、実際にどのように機能するかを確認するために、今後数日のうちにJavaで自分の提案を実装したいと思うでしょう...実行したら、ここにコードを投稿します。)
更新:
OK、今日は少し時間があり、このアイデアを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グリッドの場合:
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文字未満のすべての単語をスローします。
これのパフォーマンスに関する観察のカップルはここにあります:
これは、Victor NicolletのOCaml実装の報告されたパフォーマンスよりも約10倍遅いです。これが異なるアルゴリズム、彼が使用したより短い辞書、彼のコードがコンパイルされて私の仮想マシンで実行されるという事実、または私たちのコンピューターのパフォーマンス(私のものはWinXPを実行するIntel Q6600 @ 2.4MHz)が原因であるかどうかに関係なく、知りません。しかし、元の質問の最後に引用した他の実装の結果よりもはるかに高速です。したがって、このアルゴリズムがトライ辞書より優れているかどうかは、現時点ではわかりません。
で使用されているテーブル方式checkWordTriplets()
は、実際の回答を非常によく近似します。わずか1 3-5の言葉は、それが失敗しますから渡されたcheckWords()
テスト(参照の候補の数対実際のワード数以上を)。
上に表示されないもの:このcheckWordTriplets()
関数は約3.65msかかるため、検索プロセスでは完全に支配的です。このcheckWords()
関数は、残りの0.05〜0.20ミリ秒をほぼ占めます。
checkWordTriplets()
関数の実行時間はディクショナリのサイズに直線的に依存し、ボードのサイズとは実質的に無関係です!
の実行時間はcheckWords()
、ボードのサイズとによって除外されない単語の数によって異なりcheckWordTriplets()
ます。
上記のcheckWords()
実装は、私が思いついた最も馬鹿げた最初のバージョンです。基本的にはまったく最適化されていません。しかし、それと比較するcheckWordTriplets()
と、アプリケーションの全体的なパフォーマンスには無関係なので、心配する必要はありませんでした。しかし、ボードのサイズが大きくなると、この関数はどんどん遅くなり、やがて重要になります。次に、それも最適化する必要があります。
このコードのすばらしい点の1つは、その柔軟性です。
- ボードサイズは簡単に変更できます。10行目を更新し、に渡される文字列配列を更新し
initializeBoard()
ます。
- それはより大きな/異なるアルファベットをサポートでき、パフォーマンスのオーバーヘッドなしに 'Qu'を1文字として扱うようなものを処理できます。これを行うには、9行目と、文字が数値に変換されるいくつかの場所を更新する必要があります(現在、ASCII値から65を引くだけです)。
わかりましたが、この投稿はもう十分長いと思います。私は間違いなくあなたの質問に答えることができますが、それをコメントに移しましょう。