Je pense que vous passerez probablement la plupart de votre temps à essayer de faire correspondre des mots qui ne peuvent pas être créés par votre grille de lettres. Donc, la première chose que je ferais est d'essayer d'accélérer cette étape et cela devrait vous aider à faire la plupart du chemin.
Pour cela, je voudrais ré-exprimer la grille sous forme de tableau des "mouvements" possibles que vous indexez par la transition de lettre que vous regardez.
Commencez par attribuer à chaque lettre un numéro de tout votre alphabet (A = 0, B = 1, C = 2, ... et ainsi de suite).
Prenons cet exemple:
h b c d
e e g h
l l k l
m o f p
Et pour l'instant, utilisons l'alphabet des lettres que nous avons (généralement, vous voudrez probablement utiliser le même alphabet entier à chaque fois):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Ensuite, vous créez un tableau booléen 2D qui indique si vous avez une certaine transition de lettre disponible:
| 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
Parcourez maintenant votre liste de mots et convertissez les mots en transitions:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Vérifiez ensuite si ces transitions sont autorisées en les recherchant dans votre table:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
S'ils sont tous autorisés, il y a une chance que ce mot soit trouvé.
Par exemple, le mot "casque" peut être exclu lors de la 4ème transition (m à e: helMEt), car cette entrée dans votre tableau est fausse.
Et le mot hamster peut être exclu, car la première transition (h vers a) n'est pas autorisée (n'existe même pas dans votre table).
Maintenant, pour les quelques mots probablement que vous n'avez pas éliminés, essayez de les trouver dans la grille comme vous le faites maintenant ou comme suggéré dans certaines des autres réponses ici. Ceci afin d'éviter les faux positifs résultant de sauts entre des lettres identiques dans votre grille. Par exemple, le mot "aide" est autorisé par le tableau, mais pas par la grille.
Quelques autres conseils d'amélioration des performances sur cette idée:
Au lieu d'utiliser un tableau 2D, utilisez un tableau 1D et calculez simplement l'index de la deuxième lettre vous-même. Donc, au lieu d'un tableau 12x12 comme ci-dessus, faites un tableau 1D de longueur 144. Si vous utilisez ensuite toujours le même alphabet (c'est-à-dire un tableau 26x26 = 676x1 pour l'alphabet anglais standard), même si toutes les lettres n'apparaissent pas dans votre grille , vous pouvez pré-calculer les indices dans ce tableau 1D que vous devez tester pour faire correspondre vos mots de dictionnaire. Par exemple, les indices de «bonjour» dans l'exemple ci-dessus seraient
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
Étendez l'idée à une table 3D (exprimée sous forme de tableau 1D), c'est-à-dire toutes les combinaisons de 3 lettres autorisées. De cette façon, vous pouvez éliminer encore plus de mots immédiatement et vous réduisez le nombre de recherches de tableau pour chaque mot de 1: Pour 'hello', vous n'avez besoin que de 3 recherches de tableau: hel, ell, llo. Soit dit en passant, il sera très rapide de construire ce tableau, car il n'y a que 400 mouvements de 3 lettres possibles dans votre grille.
Pré-calculez les indices des mouvements dans votre grille que vous devez inclure dans votre table. Pour l'exemple ci-dessus, vous devez définir les entrées suivantes sur «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]
.
:
- Représentez également votre grille de jeu dans un tableau 1-D avec 16 entrées et faites pré-calculer la table en 3. contient les indices dans ce tableau.
Je suis sûr que si vous utilisez cette approche, vous pouvez exécuter votre code incroyablement rapidement, si vous avez le dictionnaire pré-calculé et déjà chargé en mémoire.
BTW: Une autre bonne chose à faire, si vous construisez un jeu, est d'exécuter ce genre de choses immédiatement en arrière-plan. Commencez à générer et à résoudre le premier jeu pendant que l'utilisateur regarde toujours l'écran de titre de votre application et met son doigt en position pour appuyer sur "Play". Ensuite, générez et résolvez le jeu suivant pendant que l'utilisateur joue le précédent. Cela devrait vous donner beaucoup de temps pour exécuter votre code.
(J'aime ce problème, donc je serai probablement tenté d'implémenter ma proposition en Java dans les prochains jours pour voir comment cela fonctionnerait réellement ... Je posterai le code ici une fois que je le ferai.)
MISE À JOUR:
Ok, j'ai eu un peu de temps aujourd'hui et j'ai implémenté cette idée en 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();
}
}
Voici quelques résultats:
Pour la grille de l'image postée dans la question d'origine (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
Pour les lettres affichées comme exemple dans la question d'origine (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
Pour la grille 5x5 suivante:
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
cela donne ceci:
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
Pour cela, j'ai utilisé la liste de mots du scrabble du tournoi TWL06 , car le lien dans la question d'origine ne fonctionne plus. Ce fichier fait 1,85 Mo, il est donc un peu plus court. Et la buildDictionary
fonction jette tous les mots de moins de 3 lettres.
Voici quelques observations sur les performances de ceci:
C'est environ 10 fois plus lent que les performances rapportées de l'implémentation OCaml de Victor Nicollet. Que cela soit dû à l'algorithme différent, au dictionnaire plus court qu'il a utilisé, au fait que son code soit compilé et que le mien s'exécute dans une machine virtuelle Java, ou aux performances de nos ordinateurs (le mien est un Intel Q6600 @ 2,4 MHz exécutant WinXP), Je ne sais pas. Mais c'est beaucoup plus rapide que les résultats des autres implémentations cités à la fin de la question d'origine. Donc, que cet algorithme soit supérieur au dictionnaire trie ou non, je ne sais pas pour l'instant.
La méthode du tableau utilisée dans checkWordTriplets()
donne une très bonne approximation des réponses réelles. Seul 1 mot sur 3 à 5 réussi échouera checkWords()
(voir le nombre de candidats par rapport au nombre de mots réels ci-dessus).
Quelque chose que vous ne pouvez pas voir ci-dessus: La checkWordTriplets()
fonction prend environ 3,65 ms et est donc pleinement dominante dans le processus de recherche. La checkWords()
fonction prend à peu près les 0,05-0,20 ms restantes.
Le temps d'exécution de la checkWordTriplets()
fonction dépend linéairement de la taille du dictionnaire et est pratiquement indépendant de la taille de la carte!
Le temps d'exécution de checkWords()
dépend de la taille du tableau et du nombre de mots non exclus par checkWordTriplets()
.
L' checkWords()
implémentation ci-dessus est la première version la plus stupide que j'ai trouvée. Il n'est fondamentalement pas du tout optimisé. Mais par rapport à checkWordTriplets()
cela, cela n'a pas d'importance pour la performance totale de l'application, donc je ne m'en suis pas inquiété. Mais , si la taille de la carte augmente, cette fonction deviendra de plus en plus lente et finira par devenir importante. Ensuite, il devrait également être optimisé.
Une bonne chose à propos de ce code est sa flexibilité:
- Vous pouvez facilement changer la taille de la carte: mettez à jour la ligne 10 et le tableau de chaînes transmis à
initializeBoard()
.
- Il peut prendre en charge des alphabets plus grands / différents et peut gérer des choses comme le traitement de «Qu» comme une seule lettre sans aucun frais généraux de performance. Pour ce faire, il faudrait mettre à jour la ligne 9 et le couple d'endroits où les caractères sont convertis en nombres (actuellement simplement en soustrayant 65 de la valeur ASCII)
D'accord, mais je pense que ce post est maintenant assez long. Je peux certainement répondre à toutes vos questions, mais passons aux commentaires.