Ich denke, Sie werden wahrscheinlich die meiste Zeit damit verbringen, Wörter zu finden, die möglicherweise nicht von Ihrem Buchstabenraster erstellt werden können. Das erste, was ich tun würde, ist zu versuchen, diesen Schritt zu beschleunigen, und das sollte Sie den größten Teil des Weges dorthin bringen.
Zu diesem Zweck würde ich das Raster erneut als Tabelle möglicher "Bewegungen" ausdrücken, die Sie durch den Buchstabenübergang indizieren, den Sie betrachten.
Beginnen Sie, indem Sie jedem Buchstaben eine Nummer aus Ihrem gesamten Alphabet zuweisen (A = 0, B = 1, C = 2, ... usw.).
Nehmen wir dieses Beispiel:
h b c d
e e g h
l l k l
m o f p
Und jetzt wollen wir das Alphabet der Buchstaben verwenden, die wir haben (normalerweise möchten Sie wahrscheinlich jedes Mal das gleiche ganze Alphabet verwenden):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Anschließend erstellen Sie ein boolesches 2D-Array, das angibt, ob ein bestimmter Buchstabenübergang verfügbar ist:
| 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
Gehen Sie nun Ihre Wortliste durch und konvertieren Sie die Wörter in Übergänge:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Überprüfen Sie dann, ob diese Übergänge zulässig sind, indem Sie sie in Ihrer Tabelle nachschlagen:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Wenn sie alle erlaubt sind, besteht die Möglichkeit, dass dieses Wort gefunden wird.
Zum Beispiel kann das Wort "Helm" beim 4. Übergang (m zu e: helMEt) ausgeschlossen werden, da dieser Eintrag in Ihrer Tabelle falsch ist.
Und das Wort Hamster kann ausgeschlossen werden, da der erste (h zu a) Übergang nicht erlaubt ist (existiert nicht einmal in Ihrer Tabelle).
Versuchen Sie nun, die wahrscheinlich wenigen verbleibenden Wörter, die Sie nicht entfernt haben, tatsächlich im Raster so zu finden, wie Sie es jetzt tun, oder wie in einigen anderen Antworten hier vorgeschlagen. Dies dient dazu, Fehlalarme zu vermeiden, die sich aus Sprüngen zwischen identischen Buchstaben in Ihrem Raster ergeben. Zum Beispiel ist das Wort "Hilfe" in der Tabelle zulässig, nicht jedoch im Raster.
Einige weitere Tipps zur Leistungsverbesserung zu dieser Idee:
Verwenden Sie anstelle eines 2D-Arrays ein 1D-Array und berechnen Sie einfach den Index des zweiten Buchstabens selbst. Erstellen Sie also anstelle eines 12x12-Arrays wie oben ein 1D-Array mit der Länge 144. Wenn Sie dann immer dasselbe Alphabet verwenden (dh ein 26x26 = 676x1-Array für das englische Standardalphabet), auch wenn nicht alle Buchstaben in Ihrem Raster angezeigt werden können Sie die Indizes in diesem 1D-Array vorberechnen, die Sie testen müssen, um mit Ihren Wörterbuchwörtern übereinzustimmen. Zum Beispiel wären die Indizes für 'Hallo' im obigen Beispiel
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
Erweitern Sie die Idee auf eine 3D-Tabelle (ausgedrückt als 1D-Array), dh alle zulässigen 3-Buchstaben-Kombinationen. Auf diese Weise können Sie sofort noch mehr Wörter entfernen und die Anzahl der Array-Suchvorgänge für jedes Wort um 1 reduzieren: Für "Hallo" benötigen Sie nur 3 Array-Suchvorgänge: hel, ell, llo. Es wird übrigens sehr schnell gehen, diese Tabelle zu erstellen, da es nur 400 mögliche 3-Buchstaben-Bewegungen in Ihrem Raster gibt.
Berechnen Sie die Indizes der Bewegungen in Ihrem Raster vor, die Sie in Ihre Tabelle aufnehmen müssen. Für das obige Beispiel müssen Sie die folgenden Einträge auf 'True' setzen:
(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]
.
:
- Stellen Sie Ihr Spielraster auch in einem 1-D-Array mit 16 Einträgen dar und lassen Sie die in 3 vorberechnete Tabelle die Indizes in diesem Array enthalten.
Ich bin sicher, wenn Sie diesen Ansatz verwenden, können Sie Ihren Code wahnsinnig schnell ausführen, wenn Sie das Wörterbuch vorberechnet und bereits in den Speicher geladen haben.
Übrigens: Eine andere nette Sache, wenn Sie ein Spiel erstellen, ist es, solche Dinge sofort im Hintergrund auszuführen. Beginnen Sie mit dem Generieren und Lösen des ersten Spiels, während der Benutzer noch auf den Titelbildschirm Ihrer App schaut und seinen Finger in Position bringt, um "Play" zu drücken. Generieren und lösen Sie dann das nächste Spiel, während der Benutzer das vorherige spielt. Das sollte Ihnen viel Zeit geben, um Ihren Code auszuführen.
(Ich mag dieses Problem, daher werde ich wahrscheinlich versucht sein, meinen Vorschlag in den nächsten Tagen in Java umzusetzen, um zu sehen, wie er tatsächlich funktionieren würde. Ich werde den Code hier veröffentlichen, sobald ich dies tue.)
AKTUALISIEREN:
Ok, ich hatte heute etwas Zeit und habe diese Idee in Java umgesetzt:
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();
}
}
Hier sind einige Ergebnisse:
Für das Raster aus dem Bild in der Originalfrage (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
Für die Briefe, die als Beispiel in der ursprünglichen Frage veröffentlicht wurden (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
Für das folgende 5x5-Raster:
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
es gibt dies:
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
Dafür habe ich die TWL06 Tournament Scrabble Word List verwendet , da der Link in der ursprünglichen Frage nicht mehr funktioniert. Diese Datei ist 1,85 MB groß und daher etwas kürzer. Und derbuildDictionary
Funktion wirft alle Wörter mit weniger als 3 Buchstaben aus.
Hier einige Beobachtungen zur Leistung:
Es ist ungefähr zehnmal langsamer als die gemeldete Leistung der OCaml-Implementierung von Victor Nicollet. Ob dies durch den unterschiedlichen Algorithmus, das kürzere Wörterbuch, das er verwendet hat, die Tatsache, dass sein Code kompiliert wird und meiner in einer virtuellen Java-Maschine ausgeführt wird, oder die Leistung unserer Computer (meiner ist ein Intel Q6600 mit 2,4 MHz unter WinXP) verursacht wird, Ich weiß es nicht. Es ist jedoch viel schneller als die Ergebnisse für die anderen Implementierungen, die am Ende der ursprünglichen Frage angegeben sind. Ob dieser Algorithmus dem Trie-Wörterbuch überlegen ist oder nicht, weiß ich derzeit noch nicht.
Die in verwendete Tabellenmethode checkWordTriplets()
liefert eine sehr gute Annäherung an die tatsächlichen Antworten. Nur 1 von 3-5 Wörtern, die von ihm bestanden wurden, besteht den checkWords()
Test nicht (siehe Anzahl der Kandidaten im Vergleich zur Anzahl der tatsächlichen Wörter oben).
Was Sie oben nicht sehen können: Die checkWordTriplets()
Funktion dauert ca. 3,65 ms und ist daher im Suchprozess voll dominant. Die checkWords()
Funktion nimmt so ziemlich die verbleibenden 0,05 bis 0,20 ms ein.
Die Ausführungszeit der checkWordTriplets()
Funktion hängt linear von der Wörterbuchgröße ab und ist praktisch unabhängig von der Kartengröße!
Die Ausführungszeit von checkWords()
hängt von der Kartengröße und der Anzahl der Wörter ab, die nicht ausgeschlossen sind checkWordTriplets()
.
Die checkWords()
obige Implementierung ist die dümmste erste Version, die ich mir ausgedacht habe. Es ist grundsätzlich überhaupt nicht optimiert. Aber im Vergleich dazu ist checkWordTriplets()
es für die Gesamtleistung der Anwendung irrelevant, also habe ich mir darüber keine Sorgen gemacht. Aber , wenn die Plattengröße größer wird, wird diese Funktion erhalten langsamer und langsamer und wird schließlich auf die Materie beginnen. Dann müsste es auch optimiert werden.
Eine schöne Sache an diesem Code ist seine Flexibilität:
- Sie können die Kartengröße einfach ändern: Aktualisieren Sie Zeile 10 und das übergebene String-Array
initializeBoard()
.
- Es kann größere / unterschiedliche Alphabete unterstützen und Dinge wie die Behandlung von 'Qu' als einen Buchstaben ohne Leistungsaufwand behandeln. Dazu müsste man Zeile 9 und die paar Stellen, an denen Zeichen in Zahlen umgewandelt werden, aktualisieren (derzeit einfach durch Subtrahieren von 65 vom ASCII-Wert).
Ok, aber ich denke jetzt ist dieser Beitrag waaaay lang genug. Ich kann definitiv alle Ihre Fragen beantworten, aber lassen Sie uns das zu den Kommentaren verschieben.