Myślę, że prawdopodobnie poświęcisz większość czasu na dopasowanie słów, których nie można zbudować za pomocą siatki listów. Pierwszą rzeczą, którą chciałbym zrobić, jest przyspieszenie tego kroku, co powinno zapewnić ci większość możliwości.
W tym celu ponownie wyraziłbym siatkę jako tabelę możliwych „ruchów”, które indeksujesz według literowanego przejścia, na które patrzysz.
Zacznij od przypisania każdej literze numeru z całego alfabetu (A = 0, B = 1, C = 2, ... itd.).
Weźmy ten przykład:
h b c d
e e g h
l l k l
m o f p
I na razie, użyjmy alfabetu liter, które mamy (zwykle prawdopodobnie za każdym razem będziesz chciał używać tego samego alfabetu):
b | c | d | e | f | g | h | k | l | m | o | p
---+---+---+---+---+---+---+---+---+---+----+----
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
Następnie tworzysz tablicę boolowską 2D, która informuje, czy dostępne jest pewne przejście literowe:
| 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
Teraz przejrzyj listę słów i przekonwertuj słowa na przejścia:
hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10
Następnie sprawdź, czy te przejścia są dozwolone, sprawdzając je w tabeli:
[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
Jeśli wszystkie są dozwolone, istnieje szansa, że to słowo zostanie odnalezione.
Na przykład słowo „kask” można wykluczyć przy 4. przejściu (m do e: helMEt), ponieważ ten wpis w tabeli jest fałszywy.
I słowo chomik można wykluczyć, ponieważ pierwsze przejście (h do a) jest niedozwolone (nawet nie istnieje w twojej tabeli).
Teraz, dla prawdopodobnie niewielu pozostałych słów, których nie wyeliminowałeś, spróbuj znaleźć je w siatce w sposób, w jaki to robisz teraz lub jak sugerowano w niektórych innych odpowiedziach tutaj. Ma to na celu uniknięcie fałszywych trafień wynikających ze skoków między identycznymi literami w siatce. Na przykład słowo „pomoc” jest dozwolone w tabeli, ale nie w siatce.
Kilka dalszych wskazówek dotyczących poprawy wydajności tego pomysłu:
Zamiast korzystać z tablicy 2D, użyj tablicy 1D i po prostu sam oblicz indeks drugiej litery. Tak więc zamiast tablicy 12x12 jak powyżej, utwórz tablicę 1D o długości 144. Jeśli zawsze będziesz używać tego samego alfabetu (tj. Tablicy 26x26 = 676x1 dla standardowego alfabetu angielskiego), nawet jeśli nie wszystkie litery pojawiają się na twojej siatce , możesz wstępnie obliczyć indeksy do tej tablicy 1D, którą musisz przetestować, aby dopasować słowa ze słownika. Na przykład, wskaźniki dla „cześć” w powyższym przykładzie byłyby
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
Rozszerz pomysł na tabelę 3D (wyrażoną jako tablica 1D), tzn. Wszystkie dozwolone kombinacje 3-literowe. W ten sposób możesz natychmiast wyeliminować jeszcze więcej słów i zmniejszyć liczbę wyszukiwań tablicowych dla każdego słowa o 1: Aby „cześć”, potrzebujesz tylko 3 wyszukiwań tablic: hel, ell, llo. Nawiasem mówiąc, zbudowanie tego stołu będzie bardzo szybkie, ponieważ w twojej sieci jest tylko 400 możliwych 3-literowych ruchów.
Oblicz wstępnie wskaźniki ruchów w siatce, które musisz uwzględnić w tabeli. W powyższym przykładzie musisz ustawić następujące wpisy na „Prawda”:
(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]
.
:
- Reprezentuj również swoją siatkę gry w tablicy 1-D z 16 wpisami, a tabela powinna być wstępnie obliczona w 3. zawierać indeksy w tej tablicy.
Jestem pewien, że jeśli zastosujesz to podejście, możesz sprawić, że Twój kod będzie działał niesamowicie szybko, jeśli masz słownik wstępnie obliczony i już załadowany do pamięci.
BTW: Inną przyjemną rzeczą, jeśli budujesz grę, jest uruchamianie tego rodzaju rzeczy natychmiast w tle. Rozpocznij generowanie i rozwiązywanie pierwszej gry, gdy użytkownik nadal patrzy na ekran tytułowy aplikacji i ustawia palec na pozycji, aby nacisnąć „Graj”. Następnie wygeneruj i rozwiąż następną grę, gdy użytkownik gra w poprzednią. To powinno dać ci dużo czasu na uruchomienie kodu.
(Podoba mi się ten problem, więc prawdopodobnie będę miał ochotę wdrożyć moją propozycję w Javie w najbliższych dniach, aby zobaczyć, jak by to faktycznie działało ... opublikuję kod tutaj, kiedy to zrobię).
AKTUALIZACJA:
Ok, miałem dzisiaj trochę czasu i wdrożyłem ten pomysł w Javie:
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();
}
}
Oto kilka wyników:
W przypadku siatki z obrazka opublikowanego w pierwotnym pytaniu (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
Dla listów opublikowanych jako przykład w pierwotnym pytaniu (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
Dla następującej siatki 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
daje to:
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
W tym celu wykorzystałem listę słów turniejowych TWL06 , ponieważ link w pierwotnym pytaniu już nie działa. Ten plik ma 1,85 MB, więc jest trochę krótszy. A buildDictionary
funkcja wyrzuca wszystkie słowa zawierające mniej niż 3 litery.
Oto kilka uwag na temat wydajności tego:
Jest około 10 razy wolniejszy niż raportowana wydajność implementacji OCaml Victora Nicolleta. Niezależnie od tego, czy jest to spowodowane przez inny algorytm, krótszy słownik, którego używa, fakt, że jego kod jest kompilowany, a mój działa na maszynie wirtualnej Java, lub wydajność naszych komputerów (moim jest Intel Q6600 @ 2,4 MHz z systemem WinXP), Nie wiem Jest to jednak znacznie szybsze niż wyniki dla innych implementacji cytowane na końcu pierwotnego pytania. Tak więc, czy ten algorytm jest lepszy od słownika trie, czy nie, nie wiem w tym momencie.
Zastosowana metoda checkWordTriplets()
tabelowa daje bardzo dobre przybliżenie do rzeczywistych odpowiedzi. Tylko 1 na 3-5 słów, które przejdzie, nie przejdzie checkWords()
testu (patrz liczba kandydatów vs. liczba faktycznych słów powyżej).
Coś, czego nie widać powyżej: checkWordTriplets()
Funkcja zajmuje około 3,65 ms i dlatego jest w pełni dominująca w procesie wyszukiwania. checkWords()
Funkcja zajmuje dość dużo pozostałe 0,05-0,20 ms.
Czas wykonania checkWordTriplets()
funkcji zależy liniowo od wielkości słownika i jest praktycznie niezależny od wielkości płyty!
Czas wykonania checkWords()
zależy od wielkości planszy i liczby słów, które nie są wykluczone checkWordTriplets()
.
Powyższe checkWords()
wdrożenie jest najgłupszą pierwszą wersją, jaką wymyśliłem. Zasadniczo nie jest w ogóle zoptymalizowany. Ale w porównaniu z checkWordTriplets()
tym nie ma znaczenia dla całkowitej wydajności aplikacji, więc nie martwiłem się o to. Ale jeśli rozmiar płyty się powiększy, ta funkcja będzie coraz wolniejsza i ostatecznie zacznie mieć znaczenie. Następnie należałoby go również zoptymalizować.
Jedną zaletą tego kodu jest jego elastyczność:
- Możesz łatwo zmienić rozmiar planszy: Zaktualizuj linię 10 i przekaż tablicę ciągów
initializeBoard()
.
- Może obsługiwać większe / różne alfabety i radzić sobie z takimi rzeczami, jak traktowanie „Qu” jako jednej litery bez żadnego nakładu wydajności. Aby to zrobić, należałoby zaktualizować wiersz 9 i kilka miejsc, w których znaki są konwertowane na liczby (obecnie po prostu odejmując 65 od wartości ASCII)
Ok, ale myślę, że do tej pory ten post jest wystarczająco długi. Z całą pewnością mogę odpowiedzieć na wszelkie pytania, ale przejdźmy do komentarzy.