Я думаю, що ви, мабуть, витратите більшу частину свого часу, намагаючись співставити слова, які неможливо побудувати за допомогою вашої літерної сітки. Отже, перше, що я зробив би - спробувати пришвидшити цей крок, і це повинно отримати вас більшу частину шляху.
Для цього я б повторно висловив сітку як таблицю можливих «рухів», які ви індексуєте за переходом літер, який ви шукаєте.
Почніть з присвоєння кожній букві число з усього алфавіту (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
Якщо їм все дозволено, є ймовірність, що це слово може бути знайдено.
Наприклад, слово "шолом" можна виключити на 4-му переході (m до e: helMEt), оскільки цей запис у вашій таблиці є помилковим.
І слово хом'як можна виключити, оскільки перший (h - a) перехід не дозволений (навіть не існує у вашій таблиці).
Тепер, напевно, дуже мало слів, які ви не усунули, спробуйте знайти їх у сітці так, як ви це робите зараз, або як це запропоновано в деяких інших відповідях тут. Це дозволяє уникнути помилкових позитивних результатів, які виникають у результаті переходів між однаковими літерами у вашій сітці. Наприклад, слово "допомога" дозволено таблицею, але не сіткою.
Деякі подальші поради щодо підвищення продуктивності щодо цієї ідеї:
Замість використання 2D масиву використовуйте 1D масив і просто обчисліть індекс другої літери самостійно. Отже, замість масиву 12х12, як вище, зробіть 1D масив довжиною 144. Якщо ви завжди використовуєте той самий алфавіт (тобто масив 26x26 = 676x1 для стандартного англійського алфавіту), навіть якщо не всі букви відображаються у вашій сітці , ви можете заздалегідь обчислити індекси в цьому 1D масиві, який потрібно перевірити, щоб відповідати вашим словам словника. Наприклад, індекси для 'привіт' у наведеному вище прикладі були б
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
Розкладіть ідею на тривимірну таблицю (виражену 1D масивом), тобто всі дозволені 3-літерні комбінації. Таким чином ви зможете усунути ще більше слів негайно, і ви зменшите кількість пошуку масивів для кожного слова на 1: Для "привіт" вам знадобляться лише 3 пошукові масиви: hel, ell, llo. До речі, скласти цю таблицю буде дуже швидко, оскільки у вашій сітці всього 400 можливих 3-х буквених ходів.
Попередньо обчисліть показники ходів у вашій сітці, які потрібно включити у вашу таблицю. Для прикладу, наведеного вище, вам потрібно встановити наступні записи на "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]
.
:
- Також представляйте свою ігрову сітку в 1-D масиві з 16 записами, а таблиця попередньо обчислена в 3. містять індекси в цьому масиві.
Я впевнений, що при використанні цього підходу ви можете змусити ваш код працювати шалено швидко, якщо словник попередньо обчислений і вже завантажений у пам'ять.
BTW: Ще одна приємна річ, якщо ви будуєте гру - це запускати подібні речі негайно на задньому плані. Почніть генерувати та вирішувати першу гру, поки користувач ще переглядає заголовковий екран у вашій програмі та вставляє палець у положення, щоб натиснути «Грати». Потім генеруйте та вирішуйте наступну гру, як користувач грає попередню. Це повинно дати вам багато часу для запуску коду.
(Мені подобається ця проблема, тому я, мабуть, спокушаюсь реалізувати свою пропозицію на Java десь у наступні дні, щоб побачити, як вона насправді буде виконуватись ... Я опублікую код тут, коли це зробити.)
ОНОВЛЕННЯ:
Гаразд, я мав деякий час сьогодні і реалізував цю ідею на 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 , оскільки посилання в оригінальному питанні більше не працює. Цей файл 1,85 Мб, тож він трохи коротший. І buildDictionary
функція викидає всі слова менше ніж 3 букви.
Ось кілька спостережень щодо ефективності цього:
Це приблизно в 10 разів повільніше, ніж повідомлялося про ефективність реалізації Віктора Ніколлета OCaml. Чи викликано це різним алгоритмом, коротким словником, який він використовував, тим, що його код складений і мій працює у віртуальній машині Java, або продуктивністю наших комп'ютерів (моя Intel Q6600 @ 2,4 МГц з WinXP), Не знаю. Але це набагато швидше, ніж результати для інших реалізацій, цитованих наприкінці оригінального питання. Тож, чи кращий цей алгоритм словнику трійки чи ні, я не знаю на даний момент.
Табличний метод, використаний у результатах, checkWordTriplets()
дає дуже гарне наближення до фактичних відповідей. Тільки 1 з 3-5 слів, які пройшли повз нього, не витримає checkWords()
тест (Дивіться кількість кандидатів проти кількості фактичних слів вище).
Щось, що ви не бачите вище: Ця checkWordTriplets()
функція займає близько 3,65 мс і тому є повністю домінуючою в процесі пошуку. checkWords()
Функція займає досить багато залишилися 0,05-0,20 мс.
Час виконання checkWordTriplets()
функції залежить лінійно від розміру словника і практично не залежить від розміру плати!
Час виконання файлу checkWords()
залежить від розміру дошки та кількості слів, які не виключаються checkWordTriplets()
.
Виконання checkWords()
вище - це найглуміша перша версія, яку я придумав. Це в основному не оптимізовано взагалі. Але порівняно з checkWordTriplets()
ним це не має значення для загальної продуктивності програми, тому я не хвилювався з цього приводу. Але , якщо розмір плати збільшується, ця функція стане повільніше і повільніше і з часом почне мати значення. Тоді його також потрібно буде оптимізувати.
Одна хороша річ у цьому коді - це його гнучкість:
- Ви можете легко змінити розмір плати: Оновіть рядок 10 та переданий масив String
initializeBoard()
.
- Він може підтримувати великі / різні алфавіти і може обробляти такі речі, як трактування "Qu" як однієї літери без будь-яких накладних результатів. Для цього потрібно буде оновити рядок 9 та пару місць, де символи перетворюються на числа (наразі просто віднімаючи 65 від значення ASCII)
Гаразд, але я думаю, що на даний момент ця посада є досить довгою. Я точно можу відповісти на будь-які питання, які у вас можуть виникнути, але давайте перейдемо до коментарів.