아마 당신은 아마 당신의 편지 그리드에 의해 만들어 질 수없는 단어와 일치 시키려고 대부분의 시간을 소비 할 것이라고 생각합니다. 그래서, 내가 할 첫 번째 일은 그 단계의 속도를 높이려고 노력하는 것입니다.
이를 위해, 나는 당신이보고있는 문자 변환에 의해 색인 할 수있는 가능한 "이동"의 표로 그리드를 다시 표현할 것이다.
알파벳 전체에서 각 문자에 숫자를 지정하여 시작하십시오 (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로) 전이가 허용되지 않기 때문에 (햄스터) 단어는 배제 할 수 있습니다 (테이블에 존재하지 않음).
지금, 당신이 제거하지 않은 아마도 거의 남지 않은 단어들에 대해, 당신이 지금하고있는 방식이나 다른 답변들에서 제안 된 것과 같이 그리드에서 실제로 단어들을 찾아보십시오. 이것은 그리드의 동일한 문자 사이에서 점프하여 발생하는 오 탐지를 피하기위한 것입니다. 예를 들어, "help"라는 단어는 표에서 허용되지만 표에서는 허용되지 않습니다.
이 아이디어에 대한 추가 성능 개선 팁 :
2D 배열을 사용하는 대신 1D 배열을 사용하고 두 번째 문자의 색인을 직접 계산하십시오. 따라서 위와 같은 12x12 배열 대신 길이가 144 인 1D 배열을 만드십시오. 그리드에 모든 문자가 표시되지 않더라도 항상 같은 알파벳 (표준 영어 알파벳의 경우 26x26 = 676x1 배열)을 사용하십시오 , 사전 단어와 일치하도록 테스트해야하는이 1D 배열로 색인을 사전 계산할 수 있습니다. 예를 들어, 위 예에서 'hello'의 색인은 다음과 같습니다.
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
아이디어를 3D 테이블 (1D 배열로 표현), 즉 허용되는 모든 3 글자 조합으로 확장하십시오. 이렇게하면 더 많은 단어를 즉시 제거 할 수 있으며 각 단어에 대한 배열 조회 수를 1 씩 줄입니다. 'hello'의 경우 hel, ell, llo의 3 가지 배열 조회 만 필요합니다. 그런데이 표를 작성하는 것은 매우 빠를 것입니다. 그리드에 가능한 3 글자 이동은 400 개뿐입니다.
표에 포함해야하는 격자의 움직임 인덱스를 미리 계산하십시오. 위 예제의 경우 다음 항목을 '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]
.
:
- 또한 16 개의 항목이있는 1 차원 배열로 게임 그리드를 나타내고 3에 미리 계산 된 테이블이 있어야합니다.이 배열에 대한 인덱스를 포함합니다.
이 접근 방식을 사용하면 사전을 사전 계산하고 메모리에 이미로드 한 경우 코드가 미친 듯이 빠르게 실행될 수 있습니다.
BTW : 또 다른 좋은 방법은 게임을 제작하는 경우 이러한 종류의 작업을 백그라운드에서 즉시 실행하는 것입니다. 사용자가 앱의 타이틀 화면을보고있는 상태에서 손가락을 제자리에 놓고 "Play"를 누르는 동안 첫 번째 게임을 생성하고 해결하십시오. 그런 다음 사용자가 이전 게임을 플레이 할 때 다음 게임을 생성하고 해결하십시오. 코드를 실행하는 데 많은 시간이 필요합니다.
(이 문제가 마음에 듭니다. 아마 다음 날 언젠가 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.85MB이므로 조금 더 짧습니다. 그리고buildDictionary
함수는 3 자 미만의 모든 단어를 버립니다.
이것의 성능에 대한 몇 가지 관찰은 다음과 같습니다.
Victor Nicollet의 OCaml 구현에 대해보고 된 성능보다 약 10 배 느립니다. 이것은 다른 알고리즘, 그가 사용한 짧은 사전, 코드가 컴파일되어 Java 가상 머신에서 실행된다는 사실 또는 우리 컴퓨터의 성능 (WinXP를 실행하는 Intel Q6600 @ 2.4MHz)에 의해 발생합니다. 모르겠어요 그러나 원래 질문의 끝에 인용 된 다른 구현의 결과보다 훨씬 빠릅니다. 따라서이 알고리즘이 트라이 사전보다 우월한 지 여부는 알지 못합니다.
사용 된 테이블 방법 checkWordTriplets()
은 실제 답변과 매우 유사합니다. 단 1 3-5 단어가 실패합니다 통과 checkWords()
테스트 (참조 후보의 수 대 실제 단어의 수 위).
위에서 볼 수없는 것 :이 checkWordTriplets()
함수는 약 3.65ms가 걸리므로 검색 프로세스에서 완전히 지배적입니다. 이 checkWords()
함수는 나머지 0.05-0.20 ms를 거의 차지합니다.
checkWordTriplets()
함수 의 실행 시간은 사전 크기에 선형 적으로 의존하며 보드 크기 와 거의 독립적입니다!
의 실행 시간은 checkWords()
보드 크기와에 의해 배제되지 않은 단어 수 에 따라 다릅니다 checkWordTriplets()
.
checkWords()
위 의 구현은 내가 생각해 낸 가장 멍청한 첫 번째 버전입니다. 기본적으로 전혀 최적화되지 않았습니다. 그러나 checkWordTriplets()
응용 프로그램의 전체 성능과 관련이 없으므로 그것에 대해 걱정하지 않았습니다. 그러나 보드 크기가 커지면이 기능은 점점 느려지고 결국 문제가 시작됩니다. 그런 다음 최적화해야합니다.
이 코드의 장점 중 하나는 유연성입니다.
- 보드 크기를 쉽게 변경할 수 있습니다 : 업데이트 라인 10 및 String 배열이로 전달되었습니다
initializeBoard()
.
- 더 크고 다른 알파벳을 지원할 수 있으며 성능 오버 헤드없이 'Qu'를 하나의 문자로 처리하는 것과 같은 것을 처리 할 수 있습니다. 이렇게하려면 9 행과 문자가 숫자로 변환되는 두 곳을 업데이트해야합니다 (현재 ASCII 값에서 65를 빼서)
좋아, 그러나 지금 까지이 게시물은 충분히 길다라고 생각합니다. 나는 당신이 가질 수있는 질문에 분명히 대답 할 수 있지만 의견으로 옮깁니다.