Penso che probabilmente passerai la maggior parte del tuo tempo a cercare di abbinare parole che non possono essere costruite con la tua griglia di lettere. Quindi, la prima cosa che vorrei fare è cercare di accelerare quel passo e che dovrebbe portarti quasi ovunque.
Per questo, vorrei ri-esprimere la griglia come una tabella di possibili "mosse" che indicizzi in base alla transizione di lettere che stai osservando.
Inizia assegnando a ogni lettera un numero dell'intero alfabeto (A = 0, B = 1, C = 2, ... e così via).
Facciamo questo esempio:
h b c d
e e g h
l l k l
m o f p
E per ora, usiamo l'alfabeto delle lettere che abbiamo (di solito probabilmente vorrai usare lo stesso intero alfabeto ogni volta):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Quindi si crea un array booleano 2D che indica se è disponibile una determinata transizione di lettere:
| 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
Ora passa attraverso l'elenco delle parole e converti le parole in transizioni:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Quindi controlla se queste transizioni sono consentite cercandole nella tabella:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Se sono tutti consentiti, è possibile che questa parola venga trovata.
Ad esempio, la parola "casco" può essere esclusa nella quarta transizione (da m a e: helMEt), poiché quella voce nella tabella è falsa.
E la parola criceto può essere esclusa, poiché la prima transizione (da h a a) non è consentita (non esiste nemmeno nella tua tabella).
Ora, per le poche parole rimanenti che probabilmente non hai eliminato, prova a trovarle nella griglia nel modo in cui lo stai facendo ora o come suggerito in alcune delle altre risposte qui. Questo per evitare falsi positivi che derivano da salti tra lettere identiche nella griglia. Ad esempio, la parola "aiuto" è consentita dalla tabella, ma non dalla griglia.
Alcuni ulteriori suggerimenti per migliorare le prestazioni su questa idea:
Invece di usare un array 2D, usa un array 1D e calcola semplicemente tu stesso l'indice della seconda lettera. Quindi, invece di un array 12x12 come sopra, crea un array 1D di lunghezza 144. Se poi usi sempre lo stesso alfabeto (cioè un array 26x26 = 676x1 per l'alfabeto inglese standard), anche se non tutte le lettere vengono visualizzate nella tua griglia , è possibile pre-calcolare gli indici in questo array 1D che è necessario testare per abbinare le parole del dizionario. Ad esempio, gli indici per "ciao" nell'esempio sopra sarebbero
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
Estendi l'idea a una tabella 3D (espressa come un array 1D), ovvero tutte le combinazioni di 3 lettere consentite. In questo modo puoi eliminare immediatamente ancora più parole e ridurre il numero di ricerche di array per ogni parola di 1: Per "ciao", hai bisogno solo di 3 ricerche di array: hel, ell, llo. A proposito, sarà molto veloce costruire questa tabella, poiché nella griglia ci sono solo 400 mosse possibili di 3 lettere.
Calcola in anticipo gli indici delle mosse nella griglia che devi includere nella tabella. Per l'esempio sopra, è necessario impostare le seguenti voci su "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]
.
:
- Rappresenta anche la griglia di gioco in un array 1-D con 16 voci e la tabella pre-calcolata in 3. contiene gli indici in questo array.
Sono sicuro che se usi questo approccio puoi far funzionare il tuo codice follemente veloce, se hai il dizionario pre-calcolato e già caricato in memoria.
A proposito: Un'altra cosa carina da fare, se stai costruendo un gioco, è eseguire questo tipo di cose immediatamente in background. Inizia a generare e risolvere il primo gioco mentre l'utente sta ancora guardando la schermata del titolo sulla tua app e posiziona il dito per premere "Gioca". Quindi genera e risolvi il gioco successivo mentre l'utente gioca a quello precedente. Questo dovrebbe darti molto tempo per eseguire il tuo codice.
(Mi piace questo problema, quindi probabilmente sarò tentato di implementare la mia proposta in Java nei prossimi giorni per vedere come potrebbe effettivamente funzionare ... Pubblicherò il codice qui una volta che lo farò.)
AGGIORNARE:
Ok, ho avuto un po 'di tempo oggi e ho implementato questa idea in 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();
}
}
Ecco alcuni risultati:
Per la griglia dall'immagine pubblicata nella domanda originale (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
Per le lettere pubblicate come esempio nella domanda originale (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
Per la seguente griglia 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
dà questo:
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
Per questo ho usato la lista di parole scrabble del torneo TWL06 , poiché il link nella domanda originale non funziona più. Questo file è 1,85 MB, quindi è un po 'più corto. E ilbuildDictionary
funzione elimina tutte le parole con meno di 3 lettere.
Ecco un paio di osservazioni sull'esecuzione di questo:
È circa 10 volte più lento delle prestazioni riportate dell'implementazione OCaml di Victor Nicollet. Se questo è causato dal diverso algoritmo, dal dizionario più breve che ha usato, dal fatto che il suo codice è compilato e il mio viene eseguito in una macchina virtuale Java, o dalle prestazioni dei nostri computer (il mio è un Intel Q6600 a 2,4 MHz che esegue WinXP), Non lo so. Ma è molto più veloce dei risultati per le altre implementazioni citate alla fine della domanda originale. Quindi, se questo algoritmo sia superiore o meno al dizionario trie, non lo so a questo punto.
Il metodo di tabella utilizzato in checkWordTriplets()
fornisce un'ottima approssimazione alle risposte effettive. Solo 1 su 3-5 parole superate non supererà il checkWords()
test (vedere il numero di candidati rispetto al numero di parole effettive sopra).
Qualcosa che non puoi vedere sopra: la checkWordTriplets()
funzione richiede circa 3,65 ms ed è quindi completamente dominante nel processo di ricerca. La checkWords()
funzione occupa praticamente i rimanenti 0,05-0,20 ms.
Il tempo di esecuzione della checkWordTriplets()
funzione dipende linearmente dalla dimensione del dizionario ed è praticamente indipendente dalla dimensione della scheda!
Il tempo di esecuzione checkWords()
dipende dalle dimensioni della scheda e dal numero di parole non escluse checkWordTriplets()
.
L' checkWords()
implementazione sopra è la prima versione più stupida che mi è venuta in mente. Fondamentalmente non è affatto ottimizzato. Ma rispetto ad checkWordTriplets()
esso è irrilevante per le prestazioni totali dell'applicazione, quindi non me ne sono preoccupato. Ma se le dimensioni della scheda aumentano, questa funzione diventerà sempre più lenta e alla fine inizierà a importare. Quindi, dovrebbe anche essere ottimizzato.
Una cosa bella di questo codice è la sua flessibilità:
- È possibile modificare facilmente le dimensioni della scheda: aggiornare la riga 10 e l'array String passato a
initializeBoard()
.
- Può supportare alfabeti più grandi / diversi e può gestire cose come il trattamento di "Qu" come una sola lettera senza alcun sovraccarico di prestazioni. Per fare ciò, bisognerebbe aggiornare la riga 9 e la coppia di posizioni in cui i caratteri vengono convertiti in numeri (attualmente sottraendo semplicemente 65 dal valore ASCII)
Ok, ma penso che ormai questo post sia abbastanza lungo. Posso sicuramente rispondere a qualsiasi domanda tu possa avere, ma passiamo ai commenti.