วิธีค้นหารายการคำที่เป็นไปได้จากเมทริกซ์ตัวอักษร [Boggle Solver]


376

เมื่อเร็ว ๆ นี้ฉันได้เล่นเกมบน iPhone ของฉันชื่อ Scramble คุณบางคนอาจรู้จักเกมนี้ในชื่อว่าเกรงกลัว เป็นหลักเมื่อเกมเริ่มต้นคุณจะได้รับเมทริกซ์ของตัวอักษรดังนี้:

F X I E
A M L O
E W B X
A S T U

เป้าหมายของเกมคือการหาคำให้มากที่สุดเท่าที่จะทำได้โดยการรวมตัวอักษรเข้าด้วยกัน คุณสามารถเริ่มต้นด้วยตัวอักษรใด ๆ และตัวอักษรทั้งหมดที่ล้อมรอบมันเป็นเกมที่ยุติธรรมและจากนั้นเมื่อคุณย้ายไปยังตัวอักษรถัดไปทุกตัวอักษรที่ล้อมรอบตัวอักษรที่มีเกมที่ยุติธรรม, ยกเว้นตัวอักษรใด ๆ ที่ใช้ก่อนหน้านี้ ดังนั้นในตารางข้างต้นสำหรับตัวอย่างเช่นผมสามารถขึ้นมาด้วยคำพูดLOB, TUX, SEA, FAMEฯลฯ คำต้องมีอย่างน้อย 3 ตัวอักษรและไม่เกินตัว NxN ซึ่งจะเป็น 16 ในเกมนี้ แต่อาจแตกต่างกันในการใช้งานบางอย่าง . ในขณะที่เกมนี้สนุกและน่าติดตาม แต่ฉันไม่เก่งและฉันอยากจะโกงนิดหน่อยด้วยการสร้างโปรแกรมที่จะให้คำศัพท์ที่ดีที่สุดแก่ฉัน (ยิ่งคำยิ่งมีคะแนนมากเท่าไหร่)

ตัวอย่างเกรงกลัว
(ที่มา: boggled.org )

ฉันโชคไม่ดีกับอัลกอริธึมหรือประสิทธิภาพของมันและอื่น ๆ ความพยายามครั้งแรกของฉันใช้พจนานุกรมเช่นนี้ (~ 2.3MB) และค้นหาเชิงเส้นพยายามจับคู่ชุดค่าผสมกับรายการพจนานุกรม ใช้เวลานานมากในการค้นหาคำที่เป็นไปได้และเนื่องจากคุณได้รับเพียง 2 นาทีต่อรอบเท่านั้นจึงไม่เพียงพอ

ฉันสนใจที่จะดูว่า Stackoverflowers ใดสามารถสร้างโซลูชันที่มีประสิทธิภาพมากขึ้นได้หรือไม่ ฉันส่วนใหญ่กำลังมองหาวิธีแก้ปัญหาโดยใช้ Big 3 Ps: Python, PHP และ Perl แม้ว่าสิ่งใดก็ตามที่มี Java หรือ C ++ ก็ยอดเยี่ยมเช่นกันเนื่องจากความเร็วเป็นสิ่งจำเป็น

โซลูชั่นปัจจุบัน :

  • Adam Rosenfield, Python, ~ 20s
  • John Fouhy, Python, ~ 3s
  • Kent Fredric, Perl, ~ 1s
  • Darius Bacon, Python, ~ 1s
  • rvarcher, VB.NET (ลิงค์สด) , ~ 1 วินาที
  • เปาโลเบอกันติโน, PHP (ลิงค์สด) , ~ 5s (~ 2s ในพื้นที่)

18
คำขอคุณลักษณะ MOAR PUZZLES
Kent Fredric

6
ในเรื่องที่เกี่ยวกับการกำหนดเวลา: ในการแก้ปัญหาของฉันเกือบตลอดเวลาใช้ในการสร้างคู่ชีวิต เมื่อทั้งคู่ถูกสร้างขึ้นมันสามารถนำมาใช้ซ้ำได้หลายครั้ง ถ้าแก้เพียงหนึ่งจิ๊กซอว์มันจะมีประสิทธิภาพมากกว่าในการใช้โครงสร้างข้อมูลที่ง่ายกว่า (เช่นชุดของคำทั้งหมดและคำนำหน้าทั้งหมด)
Adam Rosenfield

3
นอกจากนี้อดัมยังมีพจนานุกรมที่ใหญ่กว่าซึ่งเห็นได้จากจำนวนคำที่ยาวกว่าที่คำตอบของเขาใช้ พวกเขาทั้งหมดควรได้รับการทดสอบตามพจนานุกรมทั่วไป
Bradshaw Rich

2
ฉันเดาว่าไม่มีใครเล่นเกรงกลัวมาก? "Qu" เป็น "จดหมาย" หนึ่งฉบับและฉันไม่แน่ใจว่ามีวิธีการแก้ปัญหามากมายที่จับรายละเอียดเล็กน้อยนั้น ดูเหมือนว่าบางคนจะอนุญาตให้คุณใช้ "u" อย่างอิสระท่ามกลางปัญหาอื่น ๆ
Qsario

2
ฉันเพิ่งได้สิ่งนี้เป็นคำถามสัมภาษณ์และติดอยู่กับรายละเอียด ฉันถือว่ามันเป็นปัญหากราฟซึ่งก็ดี แต่โซลูชันที่นี่ใช้พื้นที่น้อยกว่ามาก ฉันกำลังเขียนโค้ดโซลูชันของฉันเองตอนนี้ ทำได้ดีสำหรับทุกคนที่มีส่วนร่วม!
ปีเตอร์เพื่อน

คำตอบ:


143

คำตอบของฉันทำงานเหมือนกับคนอื่น ๆ ที่นี่ แต่ฉันจะโพสต์มันเพราะมันดูจะเร็วกว่าโซลูชัน Python อื่น ๆ เล็กน้อยจากการตั้งค่าพจนานุกรมเร็วขึ้น (ฉันตรวจสอบสิ่งนี้เทียบกับวิธีแก้ปัญหาของ John Fouhy) หลังจากตั้งค่าเวลาในการแก้ไขจะลดลงเมื่อเกิดเสียงดัง

grid = "fxie amlo ewbx astu".split()
nrows, ncols = len(grid), len(grid[0])

# A dictionary word that could be a solution must use only the grid's
# letters and have length >= 3. (With a case-insensitive match.)
import re
alphabet = ''.join(set(''.join(grid)))
bogglable = re.compile('[' + alphabet + ']{3,}$', re.I).match

words = set(word.rstrip('\n') for word in open('words') if bogglable(word))
prefixes = set(word[:i] for word in words
               for i in range(2, len(word)+1))

def solve():
    for y, row in enumerate(grid):
        for x, letter in enumerate(row):
            for result in extending(letter, ((x, y),)):
                yield result

def extending(prefix, path):
    if prefix in words:
        yield (prefix, path)
    for (nx, ny) in neighbors(path[-1]):
        if (nx, ny) not in path:
            prefix1 = prefix + grid[ny][nx]
            if prefix1 in prefixes:
                for result in extending(prefix1, path + ((nx, ny),)):
                    yield result

def neighbors((x, y)):
    for nx in range(max(0, x-1), min(x+2, ncols)):
        for ny in range(max(0, y-1), min(y+2, nrows)):
            yield (nx, ny)

ตัวอย่างการใช้งาน:

# Print a maximal-length word and its path:
print max(solve(), key=lambda (word, path): len(word))

แก้ไข:กรองคำที่มีความยาวน้อยกว่า 3 ตัวอักษร

แก้ไข 2:ฉันสงสัยว่าทำไมโซลูชั่น Perl ของ Kent Fredric เร็วขึ้น มันกลับกลายเป็นว่าใช้การจับคู่แบบนิพจน์ทั่วไปแทนชุดอักขระ การทำเช่นเดียวกันกับ Python เกี่ยวกับการเพิ่มความเร็วเป็นสองเท่า


โปรแกรมนี้ให้ 1 คำเท่านั้น มาทำไม
เปาโล Bergantino

ฉันไม่ต้องการที่จะจมน้ำตายในการส่งออก ดูความคิดเห็นที่ด้านล่าง
Darius Bacon

6
หรือรับทุกคำที่ไม่มีพา ธ : พิมพ์ '' .join (เรียงลำดับ (ตั้งค่า (คำสำหรับ (คำ, พา ธ ) ในการแก้ ()))))
Darius Bacon

2
ใช้เวลาส่วนใหญ่ในการแยกวิเคราะห์พจนานุกรม ฉันได้ทำการวิเคราะห์คำล่วงหน้าเป็นไฟล์ "wordlines.py" ซึ่งเป็นเพียงรายการที่แต่ละคำเป็นองค์ประกอบ เพราะมันเป็นไฟล์. py มันจะกลายเป็นไฟล์. pyc ดังนั้นฉันจึงนำเข้าสิ่งนั้นแทน read (). splitlines () ในกล่องของฉันฉันกำลังแก้มันในเวลาประมาณสิบวินาที
Sean Reifschneider

1
@shellscape เป็นรหัส Python 2 Python 3 ลดความสามารถในการแยกแยะข้อโต้แย้งเช่นdef neighbors((x, y))(อย่างไม่มีจุดหมายเท่าที่ฉันเห็น) printนอกจากนี้ยังต้องมีวงเล็บรอบอาร์กิวเมนต์
Darius Bacon

116

วิธีที่เร็วที่สุดที่คุณกำลังจะได้รับอาจจะเกี่ยวข้องกับการจัดเก็บพจนานุกรมของคุณในTrie จากนั้นสร้างคิวของ triplets ( x , y , s ) โดยที่แต่ละองค์ประกอบในคิวสอดคล้องกับคำนำหน้าsของคำที่สามารถสะกดได้ในตารางสิ้นสุดที่ตำแหน่ง ( x , y ) เริ่มต้นคิวกับN x Nองค์ประกอบ (ที่Nคือขนาดของตารางของคุณ) องค์ประกอบหนึ่งสำหรับแต่ละตารางในตาราง จากนั้นอัลกอริทึมดำเนินการดังนี้:

ในขณะที่คิวไม่ว่างเปล่า:
  Dequeue a triple (x, y, s)
  สำหรับแต่ละช่อง (x ', y') ด้วยตัวอักษร c ติดกับ (x, y):
    ถ้า s + c เป็นคำเอาต์พุต s + c
    หาก s + c เป็นส่วนนำหน้าของคำให้แทรก (x ', y', s + c) ลงในคิว

หากคุณเก็บพจนานุกรมของคุณไว้ใน Trie การทดสอบว่าs + cเป็นคำหรือส่วนนำหน้าของคำสามารถทำได้ในเวลาคงที่ (หากคุณเก็บ metadata พิเศษบางอย่างในแต่ละคิวคิวเช่นชี้ไปยังโหนดปัจจุบัน ในทั้งคู่) ดังนั้นเวลาทำงานของอัลกอริทึมนี้คือ O (จำนวนคำที่สามารถสะกดได้)

[แก้ไข]นี่เป็นการใช้งานใน Python ที่ฉันเพิ่งเขียนโค้ด:

#!/usr/bin/python

class TrieNode:
    def __init__(self, parent, value):
        self.parent = parent
        self.children = [None] * 26
        self.isWord = False
        if parent is not None:
            parent.children[ord(value) - 97] = self

def MakeTrie(dictfile):
    dict = open(dictfile)
    root = TrieNode(None, '')
    for word in dict:
        curNode = root
        for letter in word.lower():
            if 97 <= ord(letter) < 123:
                nextNode = curNode.children[ord(letter) - 97]
                if nextNode is None:
                    nextNode = TrieNode(curNode, letter)
                curNode = nextNode
        curNode.isWord = True
    return root

def BoggleWords(grid, dict):
    rows = len(grid)
    cols = len(grid[0])
    queue = []
    words = []
    for y in range(cols):
        for x in range(rows):
            c = grid[y][x]
            node = dict.children[ord(c) - 97]
            if node is not None:
                queue.append((x, y, c, node))
    while queue:
        x, y, s, node = queue[0]
        del queue[0]
        for dx, dy in ((1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)):
            x2, y2 = x + dx, y + dy
            if 0 <= x2 < cols and 0 <= y2 < rows:
                s2 = s + grid[y2][x2]
                node2 = node.children[ord(grid[y2][x2]) - 97]
                if node2 is not None:
                    if node2.isWord:
                        words.append(s2)
                    queue.append((x2, y2, s2, node2))

    return words

ตัวอย่างการใช้งาน:

d = MakeTrie('/usr/share/dict/words')
print(BoggleWords(['fxie','amlo','ewbx','astu'], d))

เอาท์พุท:

['fa', 'xi', 'ie', 'io', 'el', 'am', 'axe', 'ae', 'aw', 'mi', 'mi', 'ma', 'me', ' ทองหล่อ ',' li ',' oe ',' ox ',' em ',' ea ',' ea ',' es ',' wa ',' เรา ',' wa ',' bo ',' bu ' , 'as', 'aw', 'ae', 'st', 'se', 'sa', 'tu', 'ut', 'fam', 'fam', 'fae', 'imi', 'eli', ' elm ',' elb ',' ami ',' ama ',' ame ',' aes ',' awl ',' awa ',' awe ',' รอ ',' รอ ',' mix ',' mim ',' mil ' , 'แหม่ม', 'สูงสุด', 'แม่', 'กระเพาะปลา', 'แมว', 'mem', 'mes', 'lob', 'lob', 'lox', 'lei ',' leo ',' lie ',' lim ',' oil ',' olm ',' ewe ',' eme ',' wax ',' waf ',' wae ',' wae ',' wem ' , 'wea', 'wea', 'was', 'waw', 'wae', 'bob', 'blo', 'bub', 'แต่', 'ast', 'ase', 'asa', ' awl ',' awa ',' awe ',' รอ ',' aes ',' swa ',' swa ',' เย็บ ',' ทะเล ',' ทะเล ',' เห็น ',' ทักซ์ ',' อ่าง ' , 'tut', 'twa', 'twa', 'tst', 'utu', 'fama', 'ชื่อเสียง', 'ixil', 'อิหม่าม', 'amam', 'amil', 'ambo', ' axil ',' axle ',' mimi ',' mima ',' mime ',' milo ','ไมล์ ',' mewl ',' mese ',' mesa ',' lolo ',' lobo ',' lima ',' lime ',' lime ',' limb ',' lile ',' oime ',' oleo ',' olio ' , 'oboe', 'obol', 'emim', 'emil', 'ตะวันออก', 'สบาย', 'wame', 'wawa', 'wawa', 'wawa', 'weam', 'ตะวันตก', 'wese', ' ความสิ้นหวัง ',' wase ',' wawa ',' wawa ',' boil ',' bolo ',' bole ',' bobo ',' blob ',' bleo ',' bubo ',' asem ',' stub ' , 'stut', 'swam', 'semi', 'seme', 'seam', 'seax', 'sasa', 'sawt', 'tutu', 'tuts', 'twae', 'twas', ' twae ',' ilima ',' amble ',' axile ', 'awest', 'mamie', 'mambo', 'maxim', 'mease', 'mesem', 'limax', 'limes', 'lime', 'limbo', 'limbu', 'obole', 'emesa', ' embox ',' awest ',' swami ',' famble ',' mimble ',' maxima ',' embolo ',' embole ',' embole ',' wamble ',' semese ',' semb ',' sawbwa ',' sawbwa ' ]sawbwa ']sawbwa ']

หมายเหตุ: โปรแกรมนี้จะไม่แสดงผลตัวอักษร 1 ตัวหรือกรองตามความยาวทั้งหมด มันง่ายที่จะเพิ่ม แต่ไม่เกี่ยวข้องกับปัญหาจริงๆ นอกจากนี้ยังแสดงผลลัพธ์บางคำหลาย ๆ ครั้งหากสามารถสะกดได้หลายวิธี หากคำที่ระบุสามารถสะกดได้หลายวิธี (กรณีที่แย่ที่สุด: ตัวอักษรทุกตัวในตารางเหมือนกัน (เช่น 'A') และคำเช่น 'aaaaaaaaaa' อยู่ในพจนานุกรมของคุณ) ดังนั้นเวลาในการทำงานจะเพิ่มขึ้นอย่างน่ากลัว . การกรองรายการที่ซ้ำกันและการเรียงลำดับเป็นเรื่องเล็กน้อยเนื่องจากหลังจากอัลกอริทึมเสร็จสิ้น


14
Ooo ฉันดีใจที่มีคนก้าวขึ้นไปบนจาน แม้ว่าจะได้ผล แต่ก็ไม่ได้ "จำ" ตัวอักษรที่ใช้ไปแล้วและมาพร้อมกับคำที่ต้องใช้ตัวอักษรเดียวกันสองครั้งซึ่งไม่ได้รับอนุญาต ในฐานะที่ฉันเป็นคนงี่เง่าฉันจะแก้ไขมันอย่างไร
เปาโล Bergantino

3
จริงมันจำไม่ได้ว่ามีจดหมายใดบ้างที่เยี่ยมชม แต่ไม่ได้ระบุไว้ในข้อมูลจำเพาะของคุณ =) ในการแก้ไขปัญหานั้นคุณจะต้องเพิ่มแต่ละคิวของรายการที่ตั้งที่เยี่ยมชมทั้งหมดแล้วตรวจสอบรายการนั้นก่อนที่จะเพิ่มอักขระถัดไป
Adam Rosenfield

ไม่ภายใน BoggleWords () แทนที่จะเก็บ quadruplet (x, y, s, n) คุณจะต้องเก็บ quintuplet (x, y, s, n, l) โดยที่ l คือรายการของ (x, y) ที่ได้ไปเยี่ยม จากนั้นคุณตรวจสอบแต่ละ (x2, y2) เทียบกับ l และยอมรับเฉพาะถ้ามันไม่ได้อยู่ใน l จากนั้นคุณเพิ่มลงใน l ใหม่
Adam Rosenfield

2
ฉันทำสิ่งนี้เช่นกันเมื่อฉันเบื่อเล่น Scramble ฉันคิดว่าโซลูชันแบบเรียกซ้ำ (DFS แทน BFS) ดูเซ็กซี่กว่านี้เพราะคุณสามารถเก็บชุดของเซลล์ที่ใช้งานอยู่ได้ (ดังนั้นคุณจะไม่ไปที่เซลล์เดียวกันสองครั้ง) ผู้ดูแลจำนวนมากจากนั้นเก็บรายการจำนวนมาก
Justin Scheiner

2
ไม่ควรตกอยู่ในวงวนไม่สิ้นสุด? ผมหมายถึงพูดเป็นลูกศิษย์ที่เป็นไปได้(x,y) (x+1,y+1)จากนั้น(x+1,y+1)จะถูกส่งไปยังคิว อย่างไรก็ตาม(x,y)จะเป็นผู้ติดตาม(x+1,y+1)ด้วยเช่นกันดังนั้นจะไม่นำไปสู่การย้อนกลับไปที่ไม่สิ้นสุดระหว่างพวกเขาหรือไม่
SexyBeast

39

สำหรับการเร่งความเร็วพจนานุกรมมีการแปลง / กระบวนการทั่วไปหนึ่งอย่างที่คุณสามารถทำได้เพื่อลดการเปรียบเทียบพจนานุกรมล่วงหน้า

เนื่องจากตารางด้านบนมีอักขระเพียง 16 ตัวบางตัวซ้ำกันคุณสามารถลดจำนวนคีย์ทั้งหมดในพจนานุกรมของคุณโดยการกรองรายการที่มีอักขระที่ไม่สามารถบรรลุได้

ฉันคิดว่านี่เป็นการเพิ่มประสิทธิภาพที่เห็นได้ชัด แต่ฉันไม่เห็นใครทำเลย

มันทำให้ฉันลดลงจากพจนานุกรม 200,000 ปุ่มเหลือเพียง 2,000 ปุ่มในระหว่างการป้อนข้อมูล อย่างน้อยที่สุดก็ช่วยลดโอเวอร์เฮดของหน่วยความจำได้และแน่นอนว่าจะแมปกับความเร็วที่เพิ่มขึ้นที่ไหนสักแห่งเนื่องจากหน่วยความจำไม่เร็วอย่างไม่ จำกัด

การใช้ Perl

การนำไปใช้ของฉันนั้นค่อนข้างหนักหน่วงมากเพราะฉันให้ความสำคัญกับความสามารถในการรู้เส้นทางที่ถูกต้องของสตริงที่แยกออกมาทั้งหมดไม่ใช่แค่ความถูกต้องเท่านั้น

ฉันยังมีการดัดแปลงสองสามอย่างที่ในทางทฤษฎีจะอนุญาตให้กริดที่มีรูอยู่ในนั้นทำงานได้และกริดที่มีเส้นขนาดแตกต่างกัน (สมมติว่าคุณได้รับอินพุตที่ถูกต้อง

ตัวกรองเริ่มแรกนั้นเป็นคอขวดที่สำคัญที่สุดในแอปพลิเคชันของฉันอย่างที่สงสัยก่อนหน้านี้ซึ่งแสดงความคิดเห็นว่าบรรทัดนั้นขยายจาก 1.5 ถึง 7.5

ดูเหมือนว่าการคิดเลขหลักทั้งหมดเป็นคำที่ถูกต้อง แต่ก็ค่อนข้างแน่ใจว่าเป็นเพราะไฟล์พจนานุกรมทำงานอย่างไร

มันค่อนข้างบวม แต่อย่างน้อยฉันก็ใช้Tree :: Trieจาก cpan อีกครั้ง

บางส่วนได้รับแรงบันดาลใจบางส่วนจากการใช้งานที่มีอยู่บางอย่างที่ฉันมีอยู่ในใจแล้ว

คำติชมที่สร้างสรรค์และวิธีปรับปรุงการต้อนรับ (/ ฉันสังเกตว่าเขาไม่เคยค้นหา CPAN เพื่อหาคำแก้ตัวที่ผิดพลาดแต่นี่สนุกกว่าที่จะคิดออก)

อัปเดตสำหรับเกณฑ์ใหม่

#!/usr/bin/perl 

use strict;
use warnings;

{

  # this package manages a given path through the grid.
  # Its an array of matrix-nodes in-order with
  # Convenience functions for pretty-printing the paths
  # and for extending paths as new paths.

  # Usage:
  # my $p = Prefix->new(path=>[ $startnode ]);
  # my $c = $p->child( $extensionNode );
  # print $c->current_word ;

  package Prefix;
  use Moose;

  has path => (
      isa     => 'ArrayRef[MatrixNode]',
      is      => 'rw',
      default => sub { [] },
  );
  has current_word => (
      isa        => 'Str',
      is         => 'rw',
      lazy_build => 1,
  );

  # Create a clone of this object
  # with a longer path

  # $o->child( $successive-node-on-graph );

  sub child {
      my $self    = shift;
      my $newNode = shift;
      my $f       = Prefix->new();

      # Have to do this manually or other recorded paths get modified
      push @{ $f->{path} }, @{ $self->{path} }, $newNode;
      return $f;
  }

  # Traverses $o->path left-to-right to get the string it represents.

  sub _build_current_word {
      my $self = shift;
      return join q{}, map { $_->{value} } @{ $self->{path} };
  }

  # Returns  the rightmost node on this path

  sub tail {
      my $self = shift;
      return $self->{path}->[-1];
  }

  # pretty-format $o->path

  sub pp_path {
      my $self = shift;
      my @path =
        map { '[' . $_->{x_position} . ',' . $_->{y_position} . ']' }
        @{ $self->{path} };
      return "[" . join( ",", @path ) . "]";
  }

  # pretty-format $o
  sub pp {
      my $self = shift;
      return $self->current_word . ' => ' . $self->pp_path;
  }

  __PACKAGE__->meta->make_immutable;
}

{

  # Basic package for tracking node data
  # without having to look on the grid.
  # I could have just used an array or a hash, but that got ugly.

# Once the matrix is up and running it doesn't really care so much about rows/columns,
# Its just a sea of points and each point has adjacent points.
# Relative positioning is only really useful to map it back to userspace

  package MatrixNode;
  use Moose;

  has x_position => ( isa => 'Int', is => 'rw', required => 1 );
  has y_position => ( isa => 'Int', is => 'rw', required => 1 );
  has value      => ( isa => 'Str', is => 'rw', required => 1 );
  has siblings   => (
      isa     => 'ArrayRef[MatrixNode]',
      is      => 'rw',
      default => sub { [] }
  );

# Its not implicitly uni-directional joins. It would be more effient in therory
# to make the link go both ways at the same time, but thats too hard to program around.
# and besides, this isn't slow enough to bother caring about.

  sub add_sibling {
      my $self    = shift;
      my $sibling = shift;
      push @{ $self->siblings }, $sibling;
  }

  # Convenience method to derive a path starting at this node

  sub to_path {
      my $self = shift;
      return Prefix->new( path => [$self] );
  }
  __PACKAGE__->meta->make_immutable;

}

{

  package Matrix;
  use Moose;

  has rows => (
      isa     => 'ArrayRef',
      is      => 'rw',
      default => sub { [] },
  );

  has regex => (
      isa        => 'Regexp',
      is         => 'rw',
      lazy_build => 1,
  );

  has cells => (
      isa        => 'ArrayRef',
      is         => 'rw',
      lazy_build => 1,
  );

  sub add_row {
      my $self = shift;
      push @{ $self->rows }, [@_];
  }

  # Most of these functions from here down are just builder functions,
  # or utilities to help build things.
  # Some just broken out to make it easier for me to process.
  # All thats really useful is add_row
  # The rest will generally be computed, stored, and ready to go
  # from ->cells by the time either ->cells or ->regex are called.

  # traverse all cells and make a regex that covers them.
  sub _build_regex {
      my $self  = shift;
      my $chars = q{};
      for my $cell ( @{ $self->cells } ) {
          $chars .= $cell->value();
      }
      $chars = "[^$chars]";
      return qr/$chars/i;
  }

  # convert a plain cell ( ie: [x][y] = 0 )
  # to an intelligent cell ie: [x][y] = object( x, y )
  # we only really keep them in this format temporarily
  # so we can go through and tie in neighbouring information.
  # after the neigbouring is done, the grid should be considered inoperative.

  sub _convert {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my $v    = $self->_read( $x, $y );
      my $n    = MatrixNode->new(
          x_position => $x,
          y_position => $y,
          value      => $v,
      );
      $self->_write( $x, $y, $n );
      return $n;
  }

# go through the rows/collums presently available and freeze them into objects.

  sub _build_cells {
      my $self = shift;
      my @out  = ();
      my @rows = @{ $self->{rows} };
      for my $x ( 0 .. $#rows ) {
          next unless defined $self->{rows}->[$x];
          my @col = @{ $self->{rows}->[$x] };
          for my $y ( 0 .. $#col ) {
              next unless defined $self->{rows}->[$x]->[$y];
              push @out, $self->_convert( $x, $y );
          }
      }
      for my $c (@out) {
          for my $n ( $self->_neighbours( $c->x_position, $c->y_position ) ) {
              $c->add_sibling( $self->{rows}->[ $n->[0] ]->[ $n->[1] ] );
          }
      }
      return \@out;
  }

  # given x,y , return array of points that refer to valid neighbours.
  sub _neighbours {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my @out  = ();
      for my $sx ( -1, 0, 1 ) {
          next if $sx + $x < 0;
          next if not defined $self->{rows}->[ $sx + $x ];
          for my $sy ( -1, 0, 1 ) {
              next if $sx == 0 && $sy == 0;
              next if $sy + $y < 0;
              next if not defined $self->{rows}->[ $sx + $x ]->[ $sy + $y ];
              push @out, [ $sx + $x, $sy + $y ];
          }
      }
      return @out;
  }

  sub _has_row {
      my $self = shift;
      my $x    = shift;
      return defined $self->{rows}->[$x];
  }

  sub _has_cell {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      return defined $self->{rows}->[$x]->[$y];
  }

  sub _read {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      return $self->{rows}->[$x]->[$y];
  }

  sub _write {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my $v    = shift;
      $self->{rows}->[$x]->[$y] = $v;
      return $v;
  }

  __PACKAGE__->meta->make_immutable;
}

use Tree::Trie;

sub readDict {
  my $fn = shift;
  my $re = shift;
  my $d  = Tree::Trie->new();

  # Dictionary Loading
  open my $fh, '<', $fn;
  while ( my $line = <$fh> ) {
      chomp($line);

 # Commenting the next line makes it go from 1.5 seconds to 7.5 seconds. EPIC.
      next if $line =~ $re;    # Early Filter
      $d->add( uc($line) );
  }
  return $d;
}

sub traverseGraph {
  my $d     = shift;
  my $m     = shift;
  my $min   = shift;
  my $max   = shift;
  my @words = ();

  # Inject all grid nodes into the processing queue.

  my @queue =
    grep { $d->lookup( $_->current_word ) }
    map  { $_->to_path } @{ $m->cells };

  while (@queue) {
      my $item = shift @queue;

      # put the dictionary into "exact match" mode.

      $d->deepsearch('exact');

      my $cword = $item->current_word;
      my $l     = length($cword);

      if ( $l >= $min && $d->lookup($cword) ) {
          push @words,
            $item;    # push current path into "words" if it exactly matches.
      }
      next if $l > $max;

      # put the dictionary into "is-a-prefix" mode.
      $d->deepsearch('boolean');

    siblingloop: foreach my $sibling ( @{ $item->tail->siblings } ) {
          foreach my $visited ( @{ $item->{path} } ) {
              next siblingloop if $sibling == $visited;
          }

          # given path y , iterate for all its end points
          my $subpath = $item->child($sibling);

          # create a new path for each end-point
          if ( $d->lookup( $subpath->current_word ) ) {

             # if the new path is a prefix, add it to the bottom of the queue.
              push @queue, $subpath;
          }
      }
  }
  return \@words;
}

sub setup_predetermined { 
  my $m = shift; 
  my $gameNo = shift;
  if( $gameNo == 0 ){
      $m->add_row(qw( F X I E ));
      $m->add_row(qw( A M L O ));
      $m->add_row(qw( E W B X ));
      $m->add_row(qw( A S T U ));
      return $m;
  }
  if( $gameNo == 1 ){
      $m->add_row(qw( D G H I ));
      $m->add_row(qw( K L P S ));
      $m->add_row(qw( Y E U T ));
      $m->add_row(qw( E O R N ));
      return $m;
  }
}
sub setup_random { 
  my $m = shift; 
  my $seed = shift;
  srand $seed;
  my @letters = 'A' .. 'Z' ; 
  for( 1 .. 4 ){ 
      my @r = ();
      for( 1 .. 4 ){
          push @r , $letters[int(rand(25))];
      }
      $m->add_row( @r );
  }
}

# Here is where the real work starts.

my $m = Matrix->new();
setup_predetermined( $m, 0 );
#setup_random( $m, 5 );

my $d = readDict( 'dict.txt', $m->regex );
my $c = scalar @{ $m->cells };    # get the max, as per spec

print join ",\n", map { $_->pp } @{
  traverseGraph( $d, $m, 3, $c ) ;
};

ข้อมูล arch / execution สำหรับการเปรียบเทียบ:

model name      : Intel(R) Core(TM)2 Duo CPU     T9300  @ 2.50GHz
cache size      : 6144 KB
Memory usage summary: heap total: 77057577, heap peak: 11446200, stack peak: 26448
       total calls   total memory   failed calls
 malloc|     947212       68763684              0
realloc|      11191        1045641              0  (nomove:9063, dec:4731, free:0)
 calloc|     121001        7248252              0
   free|     973159       65854762

Histogram for block sizes:
  0-15         392633  36% ==================================================
 16-31          43530   4% =====
 32-47          50048   4% ======
 48-63          70701   6% =========
 64-79          18831   1% ==
 80-95          19271   1% ==
 96-111        238398  22% ==============================
112-127          3007  <1% 
128-143        236727  21% ==============================

การเพิ่มประสิทธิภาพ Regex ให้ดียิ่งขึ้น

การเพิ่มประสิทธิภาพ regex ที่ฉันใช้นั้นไม่มีประโยชน์สำหรับพจนานุกรมที่มีหลายทางแก้และสำหรับการแก้ปัญหาแบบหลากหลายคุณจะต้องการพจนานุกรมฉบับเต็มไม่ใช่พจนานุกรมที่ถูกตัดก่อน

อย่างไรก็ตามที่กล่าวว่าสำหรับการแก้ครั้งเดียวมันเร็วจริงๆ (Perl regex อยู่ใน C! :))

นี่คือการเพิ่มรหัสที่แตกต่างกันบางส่วน:

sub readDict_nofilter {
  my $fn = shift;
  my $re = shift;
  my $d  = Tree::Trie->new();

  # Dictionary Loading
  open my $fh, '<', $fn;
  while ( my $line = <$fh> ) {
      chomp($line);
      $d->add( uc($line) );
  }
  return $d;
}

sub benchmark_io { 
  use Benchmark qw( cmpthese :hireswallclock );
   # generate a random 16 character string 
   # to simulate there being an input grid. 
  my $regexen = sub { 
      my @letters = 'A' .. 'Z' ; 
      my @lo = ();
      for( 1..16 ){ 
          push @lo , $_ ; 
      }
      my $c  = join '', @lo;
      $c = "[^$c]";
      return qr/$c/i;
  };
  cmpthese( 200 , { 
      filtered => sub { 
          readDict('dict.txt', $regexen->() );
      }, 
      unfiltered => sub {
          readDict_nofilter('dict.txt');
      }
  });
}
           s / iter กรองที่ไม่มีการกรอง
ไม่กรอง 8.16 - -94%
กรอง 0.464 1658% -

ps: 8.16 * 200 = 27 นาที


2
ฉันรู้ว่าฉันล้มเหลวในสโมสรการเพิ่มประสิทธิภาพ แต่ฉันมีปัญหาความเร็วก่อนที่ฉันจะไปทำงานจริงของรหัสและลดเวลาการป้อนข้อมูลจาก 2s เป็น 1.2s มีความหมายมากสำหรับฉัน
Kent Fredric

/ ฉันสังเกตว่ามันแปลกตอนนี้ใช้เวลาน้อยลงในการ regex และข้ามรายการมากกว่าที่ใช้ในการเพิ่มคีย์ลงในแฮช
Kent Fredric

เยี่ยมมากการใช้ Perl! ฉันจะไปเรียกใช้ตอนนี้
เปาโล Bergantino

Blerg มีปัญหาในการติดตั้ง Tree :: Trie บนเว็บเซิร์ฟเวอร์ของฉัน :(
เปาโล Bergantino

3
คุณสร้างรายงานฉบับสุดท้ายได้อย่างไร (ข้อมูล arch / execution) ดูมีประโยชน์
jmanning2k

33

คุณสามารถแบ่งปัญหาออกเป็นสองส่วน:

  1. อัลกอริทึมการค้นหาบางประเภทที่จะระบุสตริงที่เป็นไปได้ในกริด
  2. วิธีทดสอบว่าสตริงเป็นคำที่ถูกต้องหรือไม่

ตามหลักการแล้ว (2) ควรมีวิธีทดสอบว่าสตริงเป็นส่วนนำหน้าของคำที่ถูกต้องหรือไม่ - สิ่งนี้จะช่วยให้คุณตัดการค้นหาของคุณและประหยัดเวลาได้มาก

Trie ของ Adam Rosenfield เป็นทางออกสำหรับ (2) มันสวยงามและอาจเป็นสิ่งที่ผู้เชี่ยวชาญด้านอัลกอริทึมของคุณต้องการ แต่ด้วยภาษาที่ทันสมัยและคอมพิวเตอร์ที่ทันสมัยเราอาจขี้เกียจ นอกจากนี้ตามที่เคนท์แนะนำเราสามารถลดขนาดพจนานุกรมของเราโดยการละทิ้งคำที่ไม่มีตัวอักษรในตาราง นี่คืองูหลามบางส่วน:

def make_lookups(grid, fn='dict.txt'):
    # Make set of valid characters.
    chars = set()
    for word in grid:
        chars.update(word)

    words = set(x.strip() for x in open(fn) if set(x.strip()) <= chars)
    prefixes = set()
    for w in words:
        for i in range(len(w)+1):
            prefixes.add(w[:i])

    return words, prefixes

ว้าว; การทดสอบคำนำหน้าเวลาคงที่ ใช้เวลาสองสามวินาทีในการโหลดพจนานุกรมที่คุณลิงก์ แต่เพียงสองสาม :-) (โปรดสังเกตว่าwords <= prefixes)

ตอนนี้ส่วนที่ (1) ฉันมีแนวโน้มที่จะคิดในแง่ของกราฟ ดังนั้นฉันจะสร้างพจนานุกรมที่มีลักษณะดังนี้:

graph = { (x, y):set([(x0,y0), (x1,y1), (x2,y2)]), }

คือเป็นชุดของพิกัดที่คุณสามารถเข้าถึงจากตำแหน่งgraph[(x, y)] (x, y)ฉันจะเพิ่มโหนดจำลองNoneที่จะเชื่อมต่อกับทุกสิ่ง

การสร้างมันค่อนข้างงุ่มง่ามเพราะมี 8 ตำแหน่งที่เป็นไปได้และคุณต้องทำการตรวจสอบอย่าง จำกัด นี่คือบางส่วนของรหัสหลามที่สอดคล้องกัน:

def make_graph(grid):
    root = None
    graph = { root:set() }
    chardict = { root:'' }

    for i, row in enumerate(grid):
        for j, char in enumerate(row):
            chardict[(i, j)] = char
            node = (i, j)
            children = set()
            graph[node] = children
            graph[root].add(node)
            add_children(node, children, grid)

    return graph, chardict

def add_children(node, children, grid):
    x0, y0 = node
    for i in [-1,0,1]:
        x = x0 + i
        if not (0 <= x < len(grid)):
            continue
        for j in [-1,0,1]:
            y = y0 + j
            if not (0 <= y < len(grid[0])) or (i == j == 0):
                continue

            children.add((x,y))

รหัสนี้ยังสร้างการแมปพจนานุกรม(x,y)กับอักขระที่เกี่ยวข้อง นี่ทำให้ฉันเปลี่ยนรายการของตำแหน่งเป็นคำ:

def to_word(chardict, pos_list):
    return ''.join(chardict[x] for x in pos_list)

สุดท้ายเราทำการค้นหาในเชิงลึกก่อน ขั้นตอนพื้นฐานคือ:

  1. การค้นหามาถึงโหนดที่เฉพาะเจาะจง
  2. ตรวจสอบว่าเส้นทางที่อาจเป็นส่วนหนึ่งของคำ ถ้าไม่ก็อย่าสำรวจสาขานี้อีกต่อไป
  3. ตรวจสอบว่าเส้นทางเป็นคำ ถ้าเป็นเช่นนั้นเพิ่มในรายการผลลัพธ์
  4. สำรวจเด็กทุกคนที่ไม่ได้เป็นส่วนหนึ่งของเส้นทาง

งูหลาม:

def find_words(graph, chardict, position, prefix, results, words, prefixes):
    """ Arguments:
      graph :: mapping (x,y) to set of reachable positions
      chardict :: mapping (x,y) to character
      position :: current position (x,y) -- equals prefix[-1]
      prefix :: list of positions in current string
      results :: set of words found
      words :: set of valid words in the dictionary
      prefixes :: set of valid words or prefixes thereof
    """
    word = to_word(chardict, prefix)

    if word not in prefixes:
        return

    if word in words:
        results.add(word)

    for child in graph[position]:
        if child not in prefix:
            find_words(graph, chardict, child, prefix+[child], results, words, prefixes)

เรียกใช้รหัสเป็น:

grid = ['fxie', 'amlo', 'ewbx', 'astu']
g, c = make_graph(grid)
w, p = make_lookups(grid)
res = set()
find_words(g, c, None, [], res, w, p)

และตรวจสอบresเพื่อดูคำตอบ นี่คือรายการคำที่พบสำหรับตัวอย่างของคุณเรียงตามขนาด:

 ['a', 'b', 'e', 'f', 'i', 'l', 'm', 'o', 's', 't',
 'u', 'w', 'x', 'ae', 'am', 'as', 'aw', 'ax', 'bo',
 'bu', 'ea', 'el', 'em', 'es', 'fa', 'ie', 'io', 'li',
 'lo', 'ma', 'me', 'mi', 'oe', 'ox', 'sa', 'se', 'st',
 'tu', 'ut', 'wa', 'we', 'xi', 'aes', 'ame', 'ami',
 'ase', 'ast', 'awa', 'awe', 'awl', 'blo', 'but', 'elb',
 'elm', 'fae', 'fam', 'lei', 'lie', 'lim', 'lob', 'lox',
 'mae', 'maw', 'mew', 'mil', 'mix', 'oil', 'olm', 'saw',
 'sea', 'sew', 'swa', 'tub', 'tux', 'twa', 'wae', 'was',
 'wax', 'wem', 'ambo', 'amil', 'amli', 'asem', 'axil',
 'axle', 'bleo', 'boil', 'bole', 'east', 'fame', 'limb',
 'lime', 'mesa', 'mewl', 'mile', 'milo', 'oime', 'sawt',
 'seam', 'seax', 'semi', 'stub', 'swam', 'twae', 'twas',
 'wame', 'wase', 'wast', 'weam', 'west', 'amble', 'awest',
 'axile', 'embox', 'limbo', 'limes', 'swami', 'embole',
 'famble', 'semble', 'wamble']

รหัสใช้เวลา (ตัวอักษร) สองสามวินาทีในการโหลดพจนานุกรม แต่ส่วนที่เหลืออยู่ในเครื่องของฉันทันที


ดีมาก! เร็วมากเช่นกัน ฉันจะรอดูว่ามีใครก้าวขึ้นไปบนจานไหม แต่คำตอบของคุณดูดีมาก
เปาโล Bergantino

ฉันสับสนว่าทำไม "embole" เป็นคำ 6 ตัวอักษรของคุณฉันได้ 10 คำที่แตกต่างกันสำหรับที่ ดูเหมือนว่าคุณไม่อนุญาตให้เยี่ยมชมโหนดเดียวกันสองครั้งและตามที่ OP ระบุไว้นั่นเป็นเกมที่ยุติธรรม
Kent Fredric

1
ตกลงคุณอาจยังมีข้อผิดพลาดในขณะที่ทิ้ง "FAMBLE" "WAMBLE" และ "SEMBLE" ซึ่งไม่ได้แชร์อักขระ
Kent Fredric

เห็นดี! ข้อผิดพลาดในการสร้างเป็นของชุดคำนำหน้า: ฉันต้องการที่จะใช้แทนrange(len(w)+1) range(len(w))ฉันอ้างว่าwords <= prefixesแต่เห็นได้ชัดว่าฉันไม่ได้ทดสอบ: - /
John Fouhy

1
สิ่งนี้ช่วยให้ฉันเรียนรู้ว่า DFS ทำงานอย่างไรและใช้งานอย่างไร ไม่แน่ใจว่าจะมีวิธีใดที่จะแสดงความชื่นชมต่อสิ่งนี้นอกเหนือจากความคิดเห็น ขอบคุณ!
เกรแฮมสมิ ธ

23

ความพยายามของฉันใน Java ใช้เวลาประมาณ 2 วินาทีในการอ่านไฟล์และสร้างคู่ชีวิตและประมาณ 50 มิลลิวินาทีเพื่อไขปริศนา ฉันใช้พจนานุกรมที่เชื่อมโยงกับคำถาม (มีคำไม่กี่คำที่ฉันไม่ทราบว่ามีอยู่ในภาษาอังกฤษเช่น fae, ima)

0 [main] INFO gineer.bogglesolver.util.Util  - Reading the dictionary
2234 [main] INFO gineer.bogglesolver.util.Util  - Finish reading the dictionary
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: IMA
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELB
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXILE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMLI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MESA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAF
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BUT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAWT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUX

ซอร์สโค้ดประกอบด้วย 6 คลาส ฉันจะโพสต์ไว้ด้านล่าง (หากนี่ไม่ใช่วิธีปฏิบัติที่ถูกต้องใน StackOverflow โปรดบอกฉัน)

gineer.bogglesolver.Main

package gineer.bogglesolver;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class Main
{
    private final static Logger logger = Logger.getLogger(Main.class);

    public static void main(String[] args)
    {
        BasicConfigurator.configure();

        Solver solver = new Solver(4,
                        "FXIE" +
                        "AMLO" +
                        "EWBX" +
                        "ASTU");
        solver.solve();

    }
}

gineer.bogglesolver.Solver

package gineer.bogglesolver;

import gineer.bogglesolver.trie.Trie;
import gineer.bogglesolver.util.Constants;
import gineer.bogglesolver.util.Util;
import org.apache.log4j.Logger;

public class Solver
{
    private char[] puzzle;
    private int maxSize;

    private boolean[] used;
    private StringBuilder stringSoFar;

    private boolean[][] matrix;
    private Trie trie;

    private final static Logger logger = Logger.getLogger(Solver.class);

    public Solver(int size, String puzzle)
    {
        trie = Util.getTrie(size);
        matrix = Util.connectivityMatrix(size);

        maxSize = size * size;
        stringSoFar = new StringBuilder(maxSize);
        used = new boolean[maxSize];

        if (puzzle.length() == maxSize)
        {
            this.puzzle = puzzle.toCharArray();
        }
        else
        {
            logger.error("The puzzle size does not match the size specified: " + puzzle.length());
            this.puzzle = puzzle.substring(0, maxSize).toCharArray();
        }
    }

    public void solve()
    {
        for (int i = 0; i < maxSize; i++)
        {
            traverseAt(i);
        }
    }

    private void traverseAt(int origin)
    {
        stringSoFar.append(puzzle[origin]);
        used[origin] = true;

        //Check if we have a valid word
        if ((stringSoFar.length() >= Constants.MINIMUM_WORD_LENGTH) && (trie.containKey(stringSoFar.toString())))
        {
            logger.info("Found: " + stringSoFar.toString());
        }

        //Find where to go next
        for (int destination = 0; destination < maxSize; destination++)
        {
            if (matrix[origin][destination] && !used[destination] && trie.containPrefix(stringSoFar.toString() + puzzle[destination]))
            {
                traverseAt(destination);
            }
        }

        used[origin] = false;
        stringSoFar.deleteCharAt(stringSoFar.length() - 1);
    }

}

gineer.bogglesolver.trie.Node

package gineer.bogglesolver.trie;

import gineer.bogglesolver.util.Constants;

class Node
{
    Node[] children;
    boolean isKey;

    public Node()
    {
        isKey = false;
        children = new Node[Constants.NUMBER_LETTERS_IN_ALPHABET];
    }

    public Node(boolean key)
    {
        isKey = key;
        children = new Node[Constants.NUMBER_LETTERS_IN_ALPHABET];
    }

    /**
     Method to insert a string to Node and its children

     @param key the string to insert (the string is assumed to be uppercase)
     @return true if the node or one of its children is changed, false otherwise
     */
    public boolean insert(String key)
    {
        //If the key is empty, this node is a key
        if (key.length() == 0)
        {
            if (isKey)
                return false;
            else
            {
                isKey = true;
                return true;
            }
        }
        else
        {//otherwise, insert in one of its child

            int childNodePosition = key.charAt(0) - Constants.LETTER_A;
            if (children[childNodePosition] == null)
            {
                children[childNodePosition] = new Node();
                children[childNodePosition].insert(key.substring(1));
                return true;
            }
            else
            {
                return children[childNodePosition].insert(key.substring(1));
            }
        }
    }

    /**
     Returns whether key is a valid prefix for certain key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell", "hello" return true

     @param prefix the prefix to check
     @return true if the prefix is valid, false otherwise
     */
    public boolean containPrefix(String prefix)
    {
        //If the prefix is empty, return true
        if (prefix.length() == 0)
        {
            return true;
        }
        else
        {//otherwise, check in one of its child
            int childNodePosition = prefix.charAt(0) - Constants.LETTER_A;
            return children[childNodePosition] != null && children[childNodePosition].containPrefix(prefix.substring(1));
        }
    }

    /**
     Returns whether key is a valid key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell" return false

     @param key the key to check
     @return true if the key is valid, false otherwise
     */
    public boolean containKey(String key)
    {
        //If the prefix is empty, return true
        if (key.length() == 0)
        {
            return isKey;
        }
        else
        {//otherwise, check in one of its child
            int childNodePosition = key.charAt(0) - Constants.LETTER_A;
            return children[childNodePosition] != null && children[childNodePosition].containKey(key.substring(1));
        }
    }

    public boolean isKey()
    {
        return isKey;
    }

    public void setKey(boolean key)
    {
        isKey = key;
    }
}

gineer.bogglesolver.trie.Trie

package gineer.bogglesolver.trie;

public class Trie
{
    Node root;

    public Trie()
    {
        this.root = new Node();
    }

    /**
     Method to insert a string to Node and its children

     @param key the string to insert (the string is assumed to be uppercase)
     @return true if the node or one of its children is changed, false otherwise
     */
    public boolean insert(String key)
    {
        return root.insert(key.toUpperCase());
    }

    /**
     Returns whether key is a valid prefix for certain key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell", "hello" return true

     @param prefix the prefix to check
     @return true if the prefix is valid, false otherwise
     */
    public boolean containPrefix(String prefix)
    {
        return root.containPrefix(prefix.toUpperCase());
    }

    /**
     Returns whether key is a valid key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell" return false

     @param key the key to check
     @return true if the key is valid, false otherwise
     */
    public boolean containKey(String key)
    {
        return root.containKey(key.toUpperCase());
    }


}

gineer.bogglesolver.util.Constants

package gineer.bogglesolver.util;

public class Constants
{

    public static final int NUMBER_LETTERS_IN_ALPHABET = 26;
    public static final char LETTER_A = 'A';
    public static final int MINIMUM_WORD_LENGTH = 3;
    public static final int DEFAULT_PUZZLE_SIZE = 4;
}

gineer.bogglesolver.util.Util

package gineer.bogglesolver.util;

import gineer.bogglesolver.trie.Trie;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Util
{
    private final static Logger logger = Logger.getLogger(Util.class);
    private static Trie trie;
    private static int size = Constants.DEFAULT_PUZZLE_SIZE;

    /**
     Returns the trie built from the dictionary.  The size is used to eliminate words that are too long.

     @param size the size of puzzle.  The maximum lenght of words in the returned trie is (size * size)
     @return the trie that can be used for puzzle of that size
     */
    public static Trie getTrie(int size)
    {
        if ((trie != null) && size == Util.size)
            return trie;

        trie = new Trie();
        Util.size = size;

        logger.info("Reading the dictionary");
        final File file = new File("dictionary.txt");
        try
        {
            Scanner scanner = new Scanner(file);
            final int maxSize = size * size;
            while (scanner.hasNext())
            {
                String line = scanner.nextLine().replaceAll("[^\\p{Alpha}]", "");

                if (line.length() <= maxSize)
                    trie.insert(line);
            }
        }
        catch (FileNotFoundException e)
        {
            logger.error("Cannot open file", e);
        }

        logger.info("Finish reading the dictionary");
        return trie;
    }

    static boolean[] connectivityRow(int x, int y, int size)
    {
        boolean[] squares = new boolean[size * size];
        for (int offsetX = -1; offsetX <= 1; offsetX++)
        {
            for (int offsetY = -1; offsetY <= 1; offsetY++)
            {
                final int calX = x + offsetX;
                final int calY = y + offsetY;
                if ((calX >= 0) && (calX < size) && (calY >= 0) && (calY < size))
                    squares[calY * size + calX] = true;
            }
        }

        squares[y * size + x] = false;//the current x, y is false

        return squares;
    }

    /**
     Returns the matrix of connectivity between two points.  Point i can go to point j iff matrix[i][j] is true
     Square (x, y) is equivalent to point (size * y + x).  For example, square (1,1) is point 5 in a puzzle of size 4

     @param size the size of the puzzle
     @return the connectivity matrix
     */
    public static boolean[][] connectivityMatrix(int size)
    {
        boolean[][] matrix = new boolean[size * size][];
        for (int x = 0; x < size; x++)
        {
            for (int y = 0; y < size; y++)
            {
                matrix[y * size + x] = connectivityRow(x, y, size);
            }
        }
        return matrix;
    }
}

1
ฉันกำลังเปรียบเทียบเอาต์พุตของฉันกับเอาต์พุตจาก StackOverflowers อื่น ๆ และดูเหมือนว่า Adam, John และเอาต์พุต rvarcher ของหายไปบางคำ ตัวอย่างเช่น "Mwa" อยู่ในพจนานุกรม (ใช่!) แต่จะไม่ถูกส่งคืนในผลลัพธ์จาก Adam, John และ rvarcher มันถูกส่งคืนสองครั้งในลิงก์ PHP ของ Paolo
gineer

1
ฉันลองอันนี้โดยการคัดลอกมาวาง มันบอกว่า "Reading ... " และ "Finish reading ... " แต่ไม่มีอะไรปรากฏขึ้นหลังจากนั้น ไม่มีการแข่งขันปรากฏขึ้น
MikkoP

23

ฉันคิดว่าคุณอาจจะใช้เวลาส่วนใหญ่พยายามจับคู่คำที่ไม่สามารถสร้างโดยตารางจดหมายของคุณ ดังนั้นสิ่งแรกที่ฉันจะทำคือพยายามเร่งความเร็วในขั้นตอนนั้นและคุณจะได้รับประโยชน์สูงสุดจากที่นั่น

สำหรับเรื่องนี้ฉันจะแสดงตารางเป็นตาราง "การเคลื่อนไหว" ที่เป็นไปได้ที่คุณทำดัชนีโดยการเปลี่ยนแปลงตัวอักษรที่คุณกำลังดู

เริ่มต้นด้วยการกำหนดตัวเลขแต่ละตัวจากตัวอักษรทั้งหมดของคุณ (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) ครั้งแรก (ไม่มีอยู่ในตารางของคุณ)

ทีนี้สำหรับคำที่เหลืออยู่ที่คุณไม่ได้กำจัดให้ลองค้นหาคำเหล่านั้นในตารางตามที่คุณทำตอนนี้หรือตามคำแนะนำในคำตอบอื่น ๆ ที่นี่ เพื่อหลีกเลี่ยงผลบวกปลอมที่เกิดจากการกระโดดข้ามระหว่างตัวอักษรที่เหมือนกันในกริดของคุณ ตัวอย่างเช่นคำว่า "ความช่วยเหลือ" ได้รับอนุญาตจากตาราง แต่ไม่ใช่โดยกริด

เคล็ดลับการปรับปรุงประสิทธิภาพเพิ่มเติมเกี่ยวกับแนวคิดนี้:

  1. แทนที่จะใช้อาร์เรย์ 2D ให้ใช้อาร์เรย์ 1D และคำนวณดัชนีของตัวอักษรตัวที่สองด้วยตัวคุณเอง ดังนั้นแทนที่จะเป็นอาร์เรย์ 12x12 เช่นด้านบนให้สร้างอาร์เรย์ 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
    
  2. ขยายแนวคิดไปยังตาราง 3D (แสดงเป็นอาร์เรย์ 1D) นั่นคือทั้งหมดที่อนุญาตให้รวมกัน 3 ตัวอักษร ด้วยวิธีนี้คุณสามารถกำจัดคำได้มากขึ้นในทันทีและลดจำนวนการค้นหาอาร์เรย์สำหรับแต่ละคำด้วย 1: สำหรับ 'hello' คุณจะต้องค้นหาอาร์เรย์ 3 ครั้งเท่านั้น: hel, ell, llo มันจะเร็วมากในการสร้างตารางนี้โดยวิธีการเนื่องจากมีเพียง 3 ตัวอักษรที่เป็นไปได้ 400 ตัวในตารางของคุณ

  3. คำนวณดัชนีการเคลื่อนไหวล่วงหน้าในตารางของคุณที่คุณต้องการรวมไว้ในตารางของคุณ สำหรับตัวอย่างข้างต้นคุณต้องตั้งค่ารายการต่อไปนี้เป็น 'จริง':

    (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]
    .
    :
    
  4. นอกจากนี้ยังแสดงตารางเกมของคุณในอาร์เรย์ 1 มิติที่มี 16 รายการและมีตารางคำนวณล่วงหน้าใน 3 มีดัชนีในอาร์เรย์นี้

ฉันแน่ใจว่าถ้าคุณใช้วิธีนี้คุณจะสามารถทำให้โค้ดของคุณทำงานได้อย่างรวดเร็วเมามันถ้าคุณมีพจนานุกรมที่คำนวณล่วงหน้าและโหลดไปยังหน่วยความจำแล้ว

BTW: สิ่งที่ดีอีกอย่างที่ต้องทำถ้าคุณกำลังสร้างเกมคือการใช้สิ่งต่าง ๆ เหล่านี้ทันทีในพื้นหลัง เริ่มสร้างและแก้ไขเกมแรกในขณะที่ผู้ใช้ยังคงดูหน้าจอชื่อเรื่องในแอพของคุณและให้นิ้วของเขาเข้าสู่ตำแหน่งเพื่อกด "เล่น" จากนั้นสร้างและแก้ไขเกมถัดไปเมื่อผู้ใช้เล่นก่อนหน้านี้ ซึ่งควรให้เวลากับการรันโค้ดเป็นจำนวนมาก

(ฉันชอบปัญหานี้ดังนั้นฉันอาจถูกล่อลวงให้นำข้อเสนอของฉันไปใช้กับ Java ในบางวันในวันถัดไปเพื่อดูว่าจริงแล้วมันจะทำงานอย่างไร ... ฉันจะโพสต์โค้ดที่นี่เมื่อฉันทำ)

UPDATE:

ตกลงฉันมีเวลาวันนี้และนำแนวคิดนี้ไปใช้ใน 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-grid ต่อไปนี้:

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 Tournament Scrabble Word Listเนื่องจากลิงก์ในคำถามเดิมไม่ทำงานอีกต่อไป ไฟล์นี้มีขนาด 1.85MB ดังนั้นจึงสั้นกว่านี้เล็กน้อย และbuildDictionaryฟังก์ชั่นการส่งคำทั้งหมดมีน้อยกว่า 3 ตัวอักษร

นี่คือข้อสังเกตสองประการเกี่ยวกับประสิทธิภาพของสิ่งนี้:

  • ช้ากว่าการรายงานการใช้ OCaml ของ Victor Nicollet ประมาณ 10 เท่า ไม่ว่าจะเกิดจากอัลกอริทึมที่ต่างกัน, พจนานุกรมสั้น ๆ ที่เขาใช้, ความจริงที่ว่าโค้ดของเขาถูกคอมไพล์และการรันของฉันในเครื่องเสมือน Java หรือประสิทธิภาพของคอมพิวเตอร์ของเรา (ของฉันคือ Intel Q6600 @ 2.4MHz ฉันไม่รู้ แต่เร็วกว่าผลลัพธ์สำหรับการใช้งานอื่น ๆ ที่ยกมาตอนท้ายของคำถามเดิม ดังนั้นไม่ว่าอัลกอริทึมนี้จะเหนือกว่าพจนานุกรม Trie หรือไม่ฉันไม่รู้ตอนนี้

  • วิธีตารางที่ใช้ในcheckWordTriplets()การประมาณค่าที่ดีมากกับคำตอบที่เกิดขึ้นจริง มีเพียง 1 ใน 3-5 คำที่ผ่านไปเท่านั้นที่จะทำให้การcheckWords()ทดสอบล้มเหลว(ดูจำนวนผู้สมัครและจำนวนคำที่แท้จริงด้านบน)

  • สิ่งที่คุณไม่สามารถมองเห็นด้านบน: checkWordTriplets()ฟังก์ชั่นนี้ใช้เวลาประมาณ 3.65ms และโดดเด่นอย่างสมบูรณ์ในกระบวนการค้นหา checkWords()ฟังก์ชั่นใช้เวลาถึงสวยมากเหลือ 0.05-0.20 มิลลิวินาที

  • เวลาดำเนินการของcheckWordTriplets()ฟังก์ชั่นขึ้นอยู่กับขนาดพจนานุกรมเป็นเส้นตรงและไม่ขึ้นกับขนาดกระดาน!

  • เวลาการดำเนินการของขึ้นอยู่กับขนาดคณะกรรมการและจำนวนคำไม่ได้ปกครองออกโดยcheckWords()checkWordTriplets()

  • การcheckWords()ใช้งานด้านบนเป็นเวอร์ชั่นแรกที่น่าเบื่อที่สุดที่ฉันคิดไว้ มันไม่ได้ปรับให้เหมาะที่สุดเลย แต่เมื่อเทียบกับcheckWordTriplets()มันไม่เกี่ยวข้องกับประสิทธิภาพโดยรวมของแอปพลิเคชันดังนั้นฉันจึงไม่ต้องกังวลกับมัน แต่ถ้าขนาดบอร์ดใหญ่ขึ้นฟังก์ชั่นนี้จะช้าลงและช้าลงและในที่สุดก็จะเริ่มมีความหมาย จากนั้นก็จะต้องมีการปรับให้เหมาะสมเช่นกัน

  • สิ่งหนึ่งที่ดีเกี่ยวกับรหัสนี้คือความยืดหยุ่น:

    • คุณสามารถเปลี่ยนขนาดบอร์ดได้อย่างง่ายดาย: อัปเดตบรรทัดที่ 10 และอาร์เรย์สตริงที่ส่งไปยัง initializeBoard()และอาเรย์สตริงที่ผ่านมา
    • มันสามารถรองรับตัวอักษรขนาดใหญ่ / แตกต่างกันและสามารถจัดการสิ่งต่าง ๆ เช่นการรักษา 'Qu' เป็นตัวอักษรเดียวโดยไม่มีค่าใช้จ่ายในการแสดง ในการทำเช่นนี้เราจำเป็นต้องอัปเดตบรรทัดที่ 9 และสถานที่สองแห่งที่แปลงอักขระเป็นตัวเลข (ปัจจุบันเพียงลบ 65 จากค่า ASCII)

ตกลง แต่ฉันคิดว่าตอนนี้โพสต์นี้จะอยู่นานพอ แน่นอนฉันสามารถตอบคำถามใด ๆ ที่คุณอาจมี แต่ขอย้ายไปที่ความคิดเห็น


คำตอบที่ดี ฉันต้องการที่จะเห็นการใช้งานของคุณใน Java
MikkoP

@MikkoP เสร็จแล้ว! :) ใช้เวลาประมาณ 3 ชั่วโมงและรหัส 220 บรรทัด วิธีที่ดีที่จะผ่านช่วงบ่าย แจ้งให้เราทราบหากคุณมีคำถามใด ๆ เกี่ยวกับวิธีการทำงาน ... :)
Markus A.

ขอบคุณที่โพสต์รหัส! ฉันลองด้วยพจนานุกรมของฉันเองหลังจากที่ฉันได้เพิ่มการนำเข้าที่ขาดหายไป ฉันได้รับ ArrayIndexOutOfBoundException ok = possibleTriplets[entry.triplets[t]];บนเส้น อืม?
MikkoP

@MikkoP รหัสนี้ถูกเขียนขึ้นเพื่อถือว่าพจนานุกรมมีตัวอักษรตัวพิมพ์ใหญ่ AZ เท่านั้น crux อยู่ในบรรทัดที่ 34: entry.letters[i] = (byte) letter - 65;ใช้ค่า ASCII และลบออก 65 ("A") หากคุณมี Umlauts หรือตัวอักษรตัวเล็กในพจนานุกรมของคุณสิ่งนี้จะให้ค่าที่มากกว่า 31 ซึ่งไม่ได้วางแผนไว้สำหรับการตั้งค่าขนาดตัวอักษรในบรรทัดที่ 9 เพื่อรองรับตัวอักษรอื่นคุณต้องขยายบรรทัดนี้ เพื่อทำแผนที่พวกเขาในช่วงที่อนุญาตโดยขนาดตัวอักษร
Markus A.

1
@AlexanderN คุณอาจเข้าใจตรรกะอย่างถูกต้อง ฉันทำผิดพลาดในการคัดลอกตารางจดหมาย ... ขออภัย ... (แก้ไข)
Markus A.

19

น่าแปลกที่ไม่มีใครลองใช้ PHP เวอร์ชันนี้

นี่เป็นโซลูชัน Python ของ John Fouhy รุ่น PHP ที่ใช้งานได้

แม้ว่าฉันจะนำพอยน์เตอร์มาจากคำตอบของคนอื่น ๆ แต่ส่วนใหญ่ก็คัดลอกมาจากจอห์น

$boggle = "fxie
           amlo
           ewbx
           astu";

$alphabet = str_split(str_replace(array("\n", " ", "\r"), "", strtolower($boggle)));
$rows = array_map('trim', explode("\n", $boggle));
$dictionary = file("C:/dict.txt");
$prefixes = array(''=>'');
$words = array();
$regex = '/[' . implode('', $alphabet) . ']{3,}$/S';
foreach($dictionary as $k=>$value) {
    $value = trim(strtolower($value));
    $length = strlen($value);
    if(preg_match($regex, $value)) {
        for($x = 0; $x < $length; $x++) {
            $letter = substr($value, 0, $x+1);
            if($letter == $value) {
                $words[$value] = 1;
            } else {
                $prefixes[$letter] = 1;
            }
        }
    }
}

$graph = array();
$chardict = array();
$positions = array();
$c = count($rows);
for($i = 0; $i < $c; $i++) {
    $l = strlen($rows[$i]);
    for($j = 0; $j < $l; $j++) {
        $chardict[$i.','.$j] = $rows[$i][$j];
        $children = array();
        $pos = array(-1,0,1);
        foreach($pos as $z) {
            $xCoord = $z + $i;
            if($xCoord < 0 || $xCoord >= count($rows)) {
                continue;
            }
            $len = strlen($rows[0]);
            foreach($pos as $w) {
                $yCoord = $j + $w;
                if(($yCoord < 0 || $yCoord >= $len) || ($z == 0 && $w == 0)) {
                    continue;
                }
                $children[] = array($xCoord, $yCoord);
            }
        }
        $graph['None'][] = array($i, $j);
        $graph[$i.','.$j] = $children;
    }
}

function to_word($chardict, $prefix) {
    $word = array();
    foreach($prefix as $v) {
        $word[] = $chardict[$v[0].','.$v[1]];
    }
    return implode("", $word);
}

function find_words($graph, $chardict, $position, $prefix, $prefixes, &$results, $words) {
    $word = to_word($chardict, $prefix);
    if(!isset($prefixes[$word])) return false;

    if(isset($words[$word])) {
        $results[] = $word;
    }

    foreach($graph[$position] as $child) {
        if(!in_array($child, $prefix)) {
            $newprefix = $prefix;
            $newprefix[] = $child;
            find_words($graph, $chardict, $child[0].','.$child[1], $newprefix, $prefixes, $results, $words);
        }
    }
}

$solution = array();
find_words($graph, $chardict, 'None', array(), $prefixes, $solution);
print_r($solution);

นี่คือลิงค์สดถ้าคุณต้องการทดลองใช้ แม้ว่าจะใช้เวลาประมาณ 2 วินาทีในเครื่องของฉัน แต่จะใช้เวลา ~ 5s บนเว็บเซิร์ฟเวอร์ของฉัน ไม่ว่าในกรณีใดมันไม่เร็วมาก ถึงกระนั้นมันก็ค่อนข้างน่าเกลียดดังนั้นฉันสามารถจินตนาการได้ว่าเวลาจะลดลงอย่างมาก ตัวชี้ใด ๆ เกี่ยวกับวิธีการทำให้สำเร็จที่จะได้รับการชื่นชม PHP ขาดสิ่งอันดับทำให้พิกัดแปลกที่จะทำงานด้วยและฉันไม่สามารถเข้าใจได้ว่าสิ่งที่เกิดขึ้นไม่ได้ช่วยอะไรเลย

แก้ไข : การแก้ไขเล็กน้อยทำให้ใช้เวลาน้อยกว่า 1 วินาทีในเครื่อง


+1 @ "และฉันไม่สามารถเข้าใจได้ว่าเกิดอะไรขึ้นไม่ได้ช่วยเลย" ฮ่า ๆ. ฉันรักความซื่อสัตย์!
dna123

ฉันไม่รู้ PHP แต่สิ่งแรกที่ฉันจะลองคือการยก '/ [' implode ('', $ ตัวอักษร) '] {3,} $ /' ออกจากลูป นั่นคือตั้งค่าตัวแปรเป็นและใช้ตัวแปรแทนภายในลูป
Darius Bacon

ฉันค่อนข้างมั่นใจว่า PHP เก็บแคชทั่วโลกต่อเธรดของนิพจน์ทั่วไปที่คอมไพล์แล้ว แต่ฉันจะลองทำต่อไป
เปาโล Bergantino

1
@Daniel: เห็นได้ชัดว่าเป็นเว็บเซิร์ฟเวอร์ของฉัน มันไม่ได้เกิดขึ้นเมื่อฉันทำงานในพื้นที่ ยัก. ไม่รู้สึกว่าต้องการล่ามัน
Paolo Bergantino

2
สิ่งที่ควรตั้งค่าเป็นพารามิเตอร์ 7. ในฟังก์ชั่น find_words ในที่สุด?
MikkoP

16

ไม่สนใจ VB ใช่ไหม :) ฉันไม่สามารถต้านทานได้ ฉันได้แก้ไขสิ่งนี้แตกต่างจากโซลูชันที่นำเสนอที่นี่

เวลาของฉันคือ:

  • กำลังโหลดพจนานุกรมและคำนำหน้าคำลงใน hashtable: .5 ถึง 1 วินาที
  • การค้นหาคำ: ค่าเฉลี่ยต่ำกว่า 10 มิลลิวินาที

แก้ไข: เวลาโหลดพจนานุกรมบนเซิร์ฟเวอร์โฮสต์เว็บกำลังทำงานนานกว่าคอมพิวเตอร์ที่บ้านของฉันประมาณ 1 ถึง 1.5 วินาที

ฉันไม่รู้ว่าเวลาจะแย่ลงเมื่อโหลดบนเซิร์ฟเวอร์

ฉันเขียนโซลูชันเป็นเว็บเพจใน. Net myvrad.com/boggle

ฉันกำลังใช้พจนานุกรมอ้างอิงในคำถามเดิม

ตัวอักษรจะไม่ถูกนำมาใช้ซ้ำในคำ พบคำเพียง 3 ตัวอักษรหรือนานกว่านั้น

ฉันกำลังใช้ hashtable ของคำนำหน้าคำที่ไม่ซ้ำกันและคำแทน trie ฉันไม่รู้เกี่ยวกับคู่ชีวิตดังนั้นฉันจึงเรียนรู้บางสิ่งที่นั่น แนวคิดของการสร้างรายการคำนำหน้าของคำนอกเหนือจากคำที่สมบูรณ์คือสิ่งที่ในที่สุดก็ทำให้เวลาของฉันลดลงเป็นจำนวนที่น่านับถือ

อ่านความคิดเห็นของรหัสสำหรับรายละเอียดเพิ่มเติม

นี่คือรหัส:

Imports System.Collections.Generic
Imports System.IO

Partial Class boggle_Default

    'Bob Archer, 4/15/2009

    'To avoid using a 2 dimensional array in VB I'm not using typical X,Y
    'coordinate iteration to find paths.
    '
    'I have locked the code into a 4 by 4 grid laid out like so:
    ' abcd
    ' efgh
    ' ijkl
    ' mnop
    ' 
    'To find paths the code starts with a letter from a to p then
    'explores the paths available around it. If a neighboring letter
    'already exists in the path then we don't go there.
    '
    'Neighboring letters (grid points) are hard coded into
    'a Generic.Dictionary below.



    'Paths is a list of only valid Paths found. 
    'If a word prefix or word is not found the path is not
    'added and extending that path is terminated.
    Dim Paths As New Generic.List(Of String)

    'NeighborsOf. The keys are the letters a to p.
    'The value is a string of letters representing neighboring letters.
    'The string of neighboring letters is split and iterated later.
    Dim NeigborsOf As New Generic.Dictionary(Of String, String)

    'BoggleLetters. The keys are mapped to the lettered grid of a to p.
    'The values are what the user inputs on the page.
    Dim BoggleLetters As New Generic.Dictionary(Of String, String)

    'Used to store last postition of path. This will be a letter
    'from a to p.
    Dim LastPositionOfPath As String = ""

    'I found a HashTable was by far faster than a Generic.Dictionary 
    ' - about 10 times faster. This stores prefixes of words and words.
    'I determined 792773 was the number of words and unique prefixes that
    'will be generated from the dictionary file. This is a max number and
    'the final hashtable will not have that many.
    Dim HashTableOfPrefixesAndWords As New Hashtable(792773)

    'Stores words that are found.
    Dim FoundWords As New Generic.List(Of String)

    'Just to validate what the user enters in the grid.
    Dim ErrorFoundWithSubmittedLetters As Boolean = False

    Public Sub BuildAndTestPathsAndFindWords(ByVal ThisPath As String)
        'Word is the word correlating to the ThisPath parameter.
        'This path would be a series of letters from a to p.
        Dim Word As String = ""

        'The path is iterated through and a word based on the actual
        'letters in the Boggle grid is assembled.
        For i As Integer = 0 To ThisPath.Length - 1
            Word += Me.BoggleLetters(ThisPath.Substring(i, 1))
        Next

        'If my hashtable of word prefixes and words doesn't contain this Word
        'Then this isn't a word and any further extension of ThisPath will not
        'yield any words either. So exit sub to terminate exploring this path.
        If Not HashTableOfPrefixesAndWords.ContainsKey(Word) Then Exit Sub

        'The value of my hashtable is a boolean representing if the key if a word (true) or
        'just a prefix (false). If true and at least 3 letters long then yay! word found.
        If HashTableOfPrefixesAndWords(Word) AndAlso Word.Length > 2 Then Me.FoundWords.Add(Word)

        'If my List of Paths doesn't contain ThisPath then add it.
        'Remember only valid paths will make it this far. Paths not found
        'in the HashTableOfPrefixesAndWords cause this sub to exit above.
        If Not Paths.Contains(ThisPath) Then Paths.Add(ThisPath)

        'Examine the last letter of ThisPath. We are looking to extend the path
        'to our neighboring letters if any are still available.
        LastPositionOfPath = ThisPath.Substring(ThisPath.Length - 1, 1)

        'Loop through my list of neighboring letters (representing grid points).
        For Each Neighbor As String In Me.NeigborsOf(LastPositionOfPath).ToCharArray()
            'If I find a neighboring grid point that I haven't already used
            'in ThisPath then extend ThisPath and feed the new path into
            'this recursive function. (see recursive.)
            If Not ThisPath.Contains(Neighbor) Then Me.BuildAndTestPathsAndFindWords(ThisPath & Neighbor)
        Next
    End Sub

    Protected Sub ButtonBoggle_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ButtonBoggle.Click

        'User has entered the 16 letters and clicked the go button.

        'Set up my Generic.Dictionary of grid points, I'm using letters a to p -
        'not an x,y grid system.  The values are neighboring points.
        NeigborsOf.Add("a", "bfe")
        NeigborsOf.Add("b", "cgfea")
        NeigborsOf.Add("c", "dhgfb")
        NeigborsOf.Add("d", "hgc")
        NeigborsOf.Add("e", "abfji")
        NeigborsOf.Add("f", "abcgkjie")
        NeigborsOf.Add("g", "bcdhlkjf")
        NeigborsOf.Add("h", "cdlkg")
        NeigborsOf.Add("i", "efjnm")
        NeigborsOf.Add("j", "efgkonmi")
        NeigborsOf.Add("k", "fghlponj")
        NeigborsOf.Add("l", "ghpok")
        NeigborsOf.Add("m", "ijn")
        NeigborsOf.Add("n", "ijkom")
        NeigborsOf.Add("o", "jklpn")
        NeigborsOf.Add("p", "klo")

        'Retrieve letters the user entered.
        BoggleLetters.Add("a", Me.TextBox1.Text.ToLower.Trim())
        BoggleLetters.Add("b", Me.TextBox2.Text.ToLower.Trim())
        BoggleLetters.Add("c", Me.TextBox3.Text.ToLower.Trim())
        BoggleLetters.Add("d", Me.TextBox4.Text.ToLower.Trim())
        BoggleLetters.Add("e", Me.TextBox5.Text.ToLower.Trim())
        BoggleLetters.Add("f", Me.TextBox6.Text.ToLower.Trim())
        BoggleLetters.Add("g", Me.TextBox7.Text.ToLower.Trim())
        BoggleLetters.Add("h", Me.TextBox8.Text.ToLower.Trim())
        BoggleLetters.Add("i", Me.TextBox9.Text.ToLower.Trim())
        BoggleLetters.Add("j", Me.TextBox10.Text.ToLower.Trim())
        BoggleLetters.Add("k", Me.TextBox11.Text.ToLower.Trim())
        BoggleLetters.Add("l", Me.TextBox12.Text.ToLower.Trim())
        BoggleLetters.Add("m", Me.TextBox13.Text.ToLower.Trim())
        BoggleLetters.Add("n", Me.TextBox14.Text.ToLower.Trim())
        BoggleLetters.Add("o", Me.TextBox15.Text.ToLower.Trim())
        BoggleLetters.Add("p", Me.TextBox16.Text.ToLower.Trim())

        'Validate user entered something with a length of 1 for all 16 textboxes.
        For Each S As String In BoggleLetters.Keys
            If BoggleLetters(S).Length <> 1 Then
                ErrorFoundWithSubmittedLetters = True
                Exit For
            End If
        Next

        'If input is not valid then...
        If ErrorFoundWithSubmittedLetters Then
            'Present error message.
        Else
            'Else assume we have 16 letters to work with and start finding words.
            Dim SB As New StringBuilder

            Dim Time As String = String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString())

            Dim NumOfLetters As Integer = 0
            Dim Word As String = ""
            Dim TempWord As String = ""
            Dim Letter As String = ""
            Dim fr As StreamReader = Nothing
            fr = New System.IO.StreamReader(HttpContext.Current.Request.MapPath("~/boggle/dic.txt"))

            'First fill my hashtable with word prefixes and words.
            'HashTable(PrefixOrWordString, BooleanTrueIfWordFalseIfPrefix)
            While fr.Peek <> -1
                Word = fr.ReadLine.Trim()
                TempWord = ""
                For i As Integer = 0 To Word.Length - 1
                    Letter = Word.Substring(i, 1)
                    'This optimization helped quite a bit. Words in the dictionary that begin
                    'with letters that the user did not enter in the grid shouldn't go in my hashtable.
                    '
                    'I realize most of the solutions went with a Trie. I'd never heard of that before,
                    'which is one of the neat things about SO, seeing how others approach challenges
                    'and learning some best practices.
                    '
                    'However, I didn't code a Trie in my solution. I just have a hashtable with 
                    'all words in the dicitonary file and all possible prefixes for those words.
                    'A Trie might be faster but I'm not coding it now. I'm getting good times with this.
                    If i = 0 AndAlso Not BoggleLetters.ContainsValue(Letter) Then Continue While
                    TempWord += Letter
                    If Not HashTableOfPrefixesAndWords.ContainsKey(TempWord) Then
                        HashTableOfPrefixesAndWords.Add(TempWord, TempWord = Word)
                    End If
                Next
            End While

            SB.Append("Number of Word Prefixes and Words in Hashtable: " & HashTableOfPrefixesAndWords.Count.ToString())
            SB.Append("<br />")

            SB.Append("Loading Dictionary: " & Time & " - " & String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString()))
            SB.Append("<br />")

            Time = String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString())

            'This starts a path at each point on the grid an builds a path until 
            'the string of letters correlating to the path is not found in the hashtable
            'of word prefixes and words.
            Me.BuildAndTestPathsAndFindWords("a")
            Me.BuildAndTestPathsAndFindWords("b")
            Me.BuildAndTestPathsAndFindWords("c")
            Me.BuildAndTestPathsAndFindWords("d")
            Me.BuildAndTestPathsAndFindWords("e")
            Me.BuildAndTestPathsAndFindWords("f")
            Me.BuildAndTestPathsAndFindWords("g")
            Me.BuildAndTestPathsAndFindWords("h")
            Me.BuildAndTestPathsAndFindWords("i")
            Me.BuildAndTestPathsAndFindWords("j")
            Me.BuildAndTestPathsAndFindWords("k")
            Me.BuildAndTestPathsAndFindWords("l")
            Me.BuildAndTestPathsAndFindWords("m")
            Me.BuildAndTestPathsAndFindWords("n")
            Me.BuildAndTestPathsAndFindWords("o")
            Me.BuildAndTestPathsAndFindWords("p")

            SB.Append("Finding Words: " & Time & " - " & String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString()))
            SB.Append("<br />")

            SB.Append("Num of words found: " & FoundWords.Count.ToString())
            SB.Append("<br />")
            SB.Append("<br />")

            FoundWords.Sort()
            SB.Append(String.Join("<br />", FoundWords.ToArray()))

            'Output results.
            Me.LiteralBoggleResults.Text = SB.ToString()
            Me.PanelBoggleResults.Visible = True

        End If

    End Sub

End Class

ฉันจะสมมติว่าที่นี่คุณใช้ระบบ ap แทน [x] [y] เพราะส่วนหลังนั้นค่อนข้างซับซ้อนใน VB ใช่ไหม ฉันใช้เวลาหนึ่งวันในการหาอาเรย์ 2 ทางแบบไดนามิกในครั้งเดียวเช่น: อาเรย์ (1, "สวัสดี"), 1, "สวัสดี", อาเรย์ () ยังไม่ทราบวิธีการทำ ที่: P
Kent Fredric

ใน PHP และ Perl 2 อาร์เรย์ที่สลัวนั้นสนุก มันสามารถทำได้ใน VB แต่ฉันจะไม่เรียกมันว่าเป็นกระบวนการที่สนุก Dim Arr (,) As Integer = {{1,1}, {0,0}} กระบวนการของ AP ทำให้ฉันติดกริดและถามว่า 'ฉันจะไปจากที่นี่ได้อย่างไร' ฉันรู้ว่ามันเป็นวิธีการแก้ปัญหาที่เข้มงวด แต่ใช้งานได้ที่นี่
rvarcher

โอ้ฉันชอบ VB.NET ... ฉันลองใช้ URL แต่ใช้ไม่ได้ ฉันต้องสร้างรหัสของคุณใหม่เป็น Windows Forms และใช้งานได้ ขอบคุณ
อาเหม็ด Eissa

11

ทันทีที่ฉันเห็นข้อความแจ้งปัญหาฉันคิดว่า "Trie" แต่เมื่อเห็นว่ามีผู้โพสต์หลายคนที่ใช้วิธีการนั้นฉันก็มองหาวิธีอื่นที่จะแตกต่างกัน อนิจจาวิธี Trie ทำงานได้ดีขึ้น ฉันใช้โซลูชัน Perl ของ Kent บนเครื่องของฉันและใช้เวลา0.31วินาทีในการทำงานหลังจากปรับใช้ไฟล์พจนานุกรมของฉัน การใช้งาน Perl ของฉันต้องใช้เวลา0.54วินาที

นี่คือแนวทางของฉัน:

  1. สร้างแฮชการเปลี่ยนแปลงเพื่อจำลองโมเดลการเปลี่ยนทางกฎหมาย

  2. วนซ้ำตัวอักษรสามตัวที่เป็นไปได้ทั้งหมด 16 ^ 3

    • ในลูปให้ยกเว้นการเปลี่ยนที่ผิดกฎหมายและเข้าชมสี่เหลี่ยมเดียวกันซ้ำ จัดลำดับตัวอักษร 3 ตัวตามกฎหมายทั้งหมดและเก็บไว้ในแฮช
  3. จากนั้นวนดูทุกคำในพจนานุกรม

    • ยกเว้นคำที่ยาวหรือสั้นเกินไป
    • เลื่อนหน้าต่าง 3 ตัวอักษรข้ามแต่ละคำและดูว่าเป็นหนึ่งในคอมโบ 3 ตัวอักษรจากขั้นตอนที่ 2 ไม่รวมคำที่ล้มเหลว สิ่งนี้จะกำจัดส่วนที่ไม่ตรงกันมากที่สุด
    • หากยังไม่ถูกกำจัดให้ใช้อัลกอริทึมแบบเรียกซ้ำเพื่อดูว่าสามารถสร้างคำได้ด้วยการสร้างเส้นทางผ่านตัวต่อ (ส่วนนี้ช้า แต่เรียกไม่บ่อยนัก)
  4. พิมพ์คำที่ฉันพบ

    ฉันลองลำดับ 3 ตัวอักษรและ 4 ตัวอักษร แต่ลำดับ 4 ตัวอักษรทำให้โปรแกรมช้าลง

ในรหัสของฉันฉันใช้ / usr / share / dict / word สำหรับพจนานุกรมของฉัน มันมาพร้อมกับมาตรฐานบน MAC OS X และระบบ Unix จำนวนมาก คุณสามารถใช้ไฟล์อื่นได้หากต้องการ หากต้องการถอดรหัสตัวต่อที่แตกต่างกันเพียงเปลี่ยนตัวแปร @puzzle สิ่งนี้จะง่ายต่อการปรับให้เหมาะกับเมทริกซ์ที่ใหญ่ขึ้น คุณเพียงแค่ต้องเปลี่ยนแฮชการเปลี่ยน% และแฮช% legalTransitions

จุดแข็งของโซลูชันนี้คือรหัสสั้นและโครงสร้างข้อมูลง่าย

นี่คือรหัส Perl (ซึ่งใช้ตัวแปรทั่วโลกมากเกินไปฉันรู้):

#!/usr/bin/perl
use Time::HiRes  qw{ time };

sub readFile($);
sub findAllPrefixes($);
sub isWordTraceable($);
sub findWordsInPuzzle(@);

my $startTime = time;

# Puzzle to solve

my @puzzle = ( 
    F, X, I, E,
    A, M, L, O,
    E, W, B, X,
    A, S, T, U
);

my $minimumWordLength = 3;
my $maximumPrefixLength = 3; # I tried four and it slowed down.

# Slurp the word list.
my $wordlistFile = "/usr/share/dict/words";

my @words = split(/\n/, uc(readFile($wordlistFile)));
print "Words loaded from word list: " . scalar @words . "\n";

print "Word file load time: " . (time - $startTime) . "\n";
my $postLoad = time;

# Define the legal transitions from one letter position to another. 
# Positions are numbered 0-15.
#     0  1  2  3
#     4  5  6  7
#     8  9 10 11
#    12 13 14 15
my %transitions = ( 
   -1 => [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
    0 => [1,4,5], 
    1 => [0,2,4,5,6],
    2 => [1,3,5,6,7],
    3 => [2,6,7],
    4 => [0,1,5,8,9],
    5 => [0,1,2,4,6,8,9,10],
    6 => [1,2,3,5,7,9,10,11],
    7 => [2,3,6,10,11],
    8 => [4,5,9,12,13],
    9 => [4,5,6,8,10,12,13,14],
    10 => [5,6,7,9,11,13,14,15],
    11 => [6,7,10,14,15],
    12 => [8,9,13],
    13 => [8,9,10,12,14],
    14 => [9,10,11,13,15],
    15 => [10,11,14]
);

# Convert the transition matrix into a hash for easy access.
my %legalTransitions = ();
foreach my $start (keys %transitions) {
    my $legalRef = $transitions{$start};
    foreach my $stop (@$legalRef) {
        my $index = ($start + 1) * (scalar @puzzle) + ($stop + 1);
        $legalTransitions{$index} = 1;
    }
}

my %prefixesInPuzzle = findAllPrefixes($maximumPrefixLength);

print "Find prefixes time: " . (time - $postLoad) . "\n";
my $postPrefix = time;

my @wordsFoundInPuzzle = findWordsInPuzzle(@words);

print "Find words in puzzle time: " . (time - $postPrefix) . "\n";

print "Unique prefixes found: " . (scalar keys %prefixesInPuzzle) . "\n";
print "Words found (" . (scalar @wordsFoundInPuzzle) . ") :\n    " . join("\n    ", @wordsFoundInPuzzle) . "\n";

print "Total Elapsed time: " . (time - $startTime) . "\n";

###########################################

sub readFile($) {
    my ($filename) = @_;
    my $contents;
    if (-e $filename) {
        # This is magic: it opens and reads a file into a scalar in one line of code. 
        # See http://www.perl.com/pub/a/2003/11/21/slurp.html
        $contents = do { local( @ARGV, $/ ) = $filename ; <> } ; 
    }
    else {
        $contents = '';
    }
    return $contents;
}

# Is it legal to move from the first position to the second? They must be adjacent.
sub isLegalTransition($$) {
    my ($pos1,$pos2) = @_;
    my $index = ($pos1 + 1) * (scalar @puzzle) + ($pos2 + 1);
    return $legalTransitions{$index};
}

# Find all prefixes where $minimumWordLength <= length <= $maxPrefixLength
#
#   $maxPrefixLength ... Maximum length of prefix we will store. Three gives best performance. 
sub findAllPrefixes($) {
    my ($maxPrefixLength) = @_;
    my %prefixes = ();
    my $puzzleSize = scalar @puzzle;

    # Every possible N-letter combination of the letters in the puzzle 
    # can be represented as an integer, though many of those combinations
    # involve illegal transitions, duplicated letters, etc.
    # Iterate through all those possibilities and eliminate the illegal ones.
    my $maxIndex = $puzzleSize ** $maxPrefixLength;

    for (my $i = 0; $i < $maxIndex; $i++) {
        my @path;
        my $remainder = $i;
        my $prevPosition = -1;
        my $prefix = '';
        my %usedPositions = ();
        for (my $prefixLength = 1; $prefixLength <= $maxPrefixLength; $prefixLength++) {
            my $position = $remainder % $puzzleSize;

            # Is this a valid step?
            #  a. Is the transition legal (to an adjacent square)?
            if (! isLegalTransition($prevPosition, $position)) {
                last;
            }

            #  b. Have we repeated a square?
            if ($usedPositions{$position}) {
                last;
            }
            else {
                $usedPositions{$position} = 1;
            }

            # Record this prefix if length >= $minimumWordLength.
            $prefix .= $puzzle[$position];
            if ($prefixLength >= $minimumWordLength) {
                $prefixes{$prefix} = 1;
            }

            push @path, $position;
            $remainder -= $position;
            $remainder /= $puzzleSize;
            $prevPosition = $position;
        } # end inner for
    } # end outer for
    return %prefixes;
}

# Loop through all words in dictionary, looking for ones that are in the puzzle.
sub findWordsInPuzzle(@) {
    my @allWords = @_;
    my @wordsFound = ();
    my $puzzleSize = scalar @puzzle;
WORD: foreach my $word (@allWords) {
        my $wordLength = length($word);
        if ($wordLength > $puzzleSize || $wordLength < $minimumWordLength) {
            # Reject word as too short or too long.
        }
        elsif ($wordLength <= $maximumPrefixLength ) {
            # Word should be in the prefix hash.
            if ($prefixesInPuzzle{$word}) {
                push @wordsFound, $word;
            }
        }
        else {
            # Scan through the word using a window of length $maximumPrefixLength, looking for any strings not in our prefix list.
            # If any are found that are not in the list, this word is not possible.
            # If no non-matches are found, we have more work to do.
            my $limit = $wordLength - $maximumPrefixLength + 1;
            for (my $startIndex = 0; $startIndex < $limit; $startIndex ++) {
                if (! $prefixesInPuzzle{substr($word, $startIndex, $maximumPrefixLength)}) {
                    next WORD;
                }
            }
            if (isWordTraceable($word)) {
                # Additional test necessary: see if we can form this word by following legal transitions
                push @wordsFound, $word;
            }
        }

    }
    return @wordsFound;
}

# Is it possible to trace out the word using only legal transitions?
sub isWordTraceable($) {
    my $word = shift;
    return traverse([split(//, $word)], [-1]); # Start at special square -1, which may transition to any square in the puzzle.
}

# Recursively look for a path through the puzzle that matches the word.
sub traverse($$) {
    my ($lettersRef, $pathRef) = @_;
    my $index = scalar @$pathRef - 1;
    my $position = $pathRef->[$index];
    my $letter = $lettersRef->[$index];
    my $branchesRef =  $transitions{$position};
BRANCH: foreach my $branch (@$branchesRef) {
            if ($puzzle[$branch] eq $letter) {
                # Have we used this position yet?
                foreach my $usedBranch (@$pathRef) {
                    if ($usedBranch == $branch) {
                        next BRANCH;
                    }
                }
                if (scalar @$lettersRef == $index + 1) {
                    return 1; # End of word and success.
                }
                push @$pathRef, $branch;
                if (traverse($lettersRef, $pathRef)) {
                    return 1; # Recursive success.
                }
                else {
                    pop @$pathRef;
                }
            }
        }
    return 0; # No path found. Failed.
}

ตำแหน่งของพจนานุกรมเปลี่ยนไปหรือไม่ ฉันพยายามค้นหาคำในพจนานุกรมเนื่องจากฉันต้องการเปรียบเทียบโซลูชันของฉันกับทุกคน แต่ฉันไม่พบมันในลิงก์ที่ให้ไว้ที่ / usr / share / dict ฉันรู้ว่ามันค่อนข้างด้ายเก่า แต่มันจะดีถ้าคุณสามารถชี้ฉัน ขอบคุณล่วงหน้าสำหรับความช่วยเหลือของ.
Naman

ไม่มี Mac ของฉันมีประโยชน์ในขณะนี้ สิ่งที่คุณต้องมีคือไฟล์ที่มีคำภาษาอังกฤษหนึ่งบรรทัดต่อบรรทัดคั่นด้วยบรรทัดใหม่ คุณอาจพบไฟล์ดังกล่าวบนอินเทอร์เน็ต หนึ่งอยู่ที่นี่: mieliestronk.com/corncob_lowercase.txt แต่อาจมีรายการที่มีคำมากกว่านั้น
Paul Chernoch

ขอบคุณมากสำหรับคำตอบ ฉันพบว่าในไฟล์ Ubuntu
Naman

9

ฉันรู้ว่าฉันสายเกินไปแต่ฉันก็ทำสิ่งเหล่านี้เมื่อไม่นานมานี้ในPHP - เพื่อความสนุกสนานเช่นกัน ...

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu พบ 75 คำ (133 แต้ม) ใน0.90108 วินาที

F.........X..I..............E............... A......................................M..............................L............................O............................... E....................W............................B..........................X A..................S..................................................T.................U....

ให้ข้อบ่งชี้ว่าโปรแกรมกำลังทำอะไรอยู่ - ตัวอักษรแต่ละตัวคือตำแหน่งที่มันเริ่มมองผ่านรูปแบบในขณะที่แต่ละตัว '.' แสดงเส้นทางที่พยายามทำ ยิ่ง '.' มีการค้นหาเพิ่มเติมอีก

แจ้งให้เราทราบหากคุณต้องการรหัส ... มันเป็นการผสมผสานที่น่ากลัวของ PHP และ HTML ที่ไม่ได้หมายถึงการมองเห็นแสงของวันดังนั้นฉันไม่กล้าโพสต์ที่นี่: P


9

ฉันใช้เวลา 3 เดือนทำงานหาวิธีแก้ปัญหาแผงวงจรเกรงใจ 5x5 ที่ดีที่สุด 10 จุด

ขณะนี้ปัญหาได้รับการแก้ไขแล้วและได้รับการเปิดเผยโดยสมบูรณ์ใน 5 หน้าเว็บ กรุณาติดต่อฉันด้วยคำถาม

อัลกอริธึมการวิเคราะห์บอร์ดใช้สแต็คที่ชัดเจนในการหลอกสี่เหลี่ยมวนซ้ำผ่านกระดานซ้ำโดยใช้กราฟคำที่มีการกำกับด้วยข้อมูลลูกโดยตรงและกลไกการติดตามการประทับเวลา นี่อาจเป็นโครงสร้างข้อมูลพจนานุกรมที่ทันสมัยที่สุดในโลก

โครงการนี้ประเมินบอร์ด 10,000 ดีมากต่อวินาทีบน quad core (9500+ คะแนน)

หน้าเว็บหลัก:

DeepSearch.c - http://www.pathcom.com/~vadco/deep.html

องค์ประกอบหน้าเว็บ:

กระดานคะแนนที่ดีที่สุด - http://www.pathcom.com/~vadco/binary.html

โครงสร้างพจนานุกรมขั้นสูง - http://www.pathcom.com/~vadco/adtdawg.html

อัลกอริทึมการวิเคราะห์บอร์ด - http://www.pathcom.com/~vadco/guns.html

การประมวลผลแบบแบตช์แบบขนาน - http://www.pathcom.com/~vadco/parallel.html

- งานที่ครอบคลุมนี้จะให้ความสนใจเฉพาะบุคคลที่ต้องการสิ่งที่ดีที่สุดเท่านั้น


4
การวิเคราะห์ของคุณน่าสนใจ แต่ผลลัพธ์ของคุณไม่ใช่เทคนิคเกรงกลัว เกมเกรงกลัว 5x5 รวมถึงการตายหนึ่งครั้งที่มีใบหน้า BJKQXZ การใช้งานของคุณนั้นไม่รวมตัวอักษรทั้งหมดเหล่านี้อย่างชัดเจนดังนั้นตำแหน่งคณะกรรมการจึงเป็นไปไม่ได้ในเกม Boggle จริง
MarkPflug

4

อัลกอริทึมการค้นหาของคุณลดรายการคำอย่างต่อเนื่องเมื่อการค้นหาของคุณดำเนินการต่อหรือไม่

ตัวอย่างเช่นในการค้นหาด้านบนมีเพียง 13 ตัวอักษรที่คำของคุณสามารถเริ่มต้นด้วย (ลดได้อย่างมีประสิทธิภาพถึงครึ่งเป็นตัวอักษรเริ่มต้นจำนวนมาก)

เมื่อคุณเพิ่มการเรียงสับเปลี่ยนจดหมายมากขึ้นมันจะลดชุดคำที่มีอยู่ลดการค้นหาที่จำเป็นต่อไป

ฉันจะเริ่มต้นที่นั่น


4

ฉันต้องคิดให้มากขึ้นเพื่อแก้ปัญหาที่สมบูรณ์ แต่เพื่อให้เกิดประโยชน์สูงสุดฉันสงสัยว่ามันอาจจะคุ้มค่าที่จะคำนวณตารางความถี่ของดิแกรมและตรีโกณมิติ (รวมกัน 2 และ 3 ตัวอักษร) ตามความเหมาะสมทั้งหมด คำจากพจนานุกรมของคุณและใช้สิ่งนี้เพื่อจัดลำดับความสำคัญการค้นหาของคุณ ฉันจะไปกับตัวอักษรเริ่มต้นของคำ ดังนั้นหากพจนานุกรมของคุณมีคำว่า "อินเดีย", "น้ำ", "สุดขีด" และ "พิเศษ" ดังนั้นตารางที่คำนวณล่วงหน้าของคุณอาจเป็น:

'IN': 1
'WA': 1
'EX': 2

จากนั้นค้นหา digrams เหล่านี้ตามลำดับของคนธรรมดาสามัญ (คนแรก EX, ตามด้วย WA / IN)


4

ก่อนอื่นให้อ่านว่านักออกแบบภาษา C # หนึ่งคนแก้ปัญหาที่เกี่ยวข้องได้อย่างไร: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .

เช่นเดียวกับคุณคุณสามารถเริ่มต้นด้วยพจนานุกรมและคำว่า canonacalize ด้วยการสร้างพจนานุกรมจากตัวอักษรเรียงลำดับตามตัวอักษรไปยังรายการคำที่สามารถสะกดจากตัวอักษรเหล่านั้น

จากนั้นเริ่มสร้างคำที่เป็นไปได้จากกระดานและค้นหาคำเหล่านั้น ฉันสงสัยว่าจะทำให้คุณสวยได้ไกล


4

ฉันขอแนะนำให้สร้างต้นไม้ของตัวอักษรตามคำ ต้นไม้จะประกอบด้วยโครงสร้างจดหมายเช่นนี้:

letter: char
isWord: boolean

จากนั้นคุณสร้างต้นไม้โดยเพิ่มความลึกแต่ละตัวอักษรใหม่ กล่าวอีกนัยหนึ่งในระดับแรกจะมีตัวอักษร จากต้นไม้เหล่านั้นแต่ละต้นจะมีอีก 26 รายการและจนกว่าคุณจะสะกดคำทั้งหมด แขวนลงบนต้นไม้ที่แยกวิเคราะห์แล้วและจะทำให้คำตอบที่เป็นไปได้ทั้งหมดเพื่อค้นหาได้เร็วขึ้น

ด้วยต้นไม้ที่แยกวิเคราะห์นี้คุณสามารถค้นหาคำตอบได้อย่างรวดเร็ว นี่คือรหัสเทียม:

BEGIN: 
    For each letter:
        if the struct representing it on the current depth has isWord == true, enter it as an answer.
        Cycle through all its neighbors; if there is a child of the current node corresponding to the letter, recursively call BEGIN on it.

นี่อาจเป็นการเร่งความเร็วด้วยการเขียนโปรแกรมแบบไดนามิกเล็กน้อย ตัวอย่างเช่นในตัวอย่างของคุณ 'A's สองอันนั้นอยู่ถัดจาก' E 'และ' W 'ซึ่ง (จากจุดที่พวกเขาไปถึง) จะเหมือนกัน ฉันไม่มีเวลามากพอที่จะสะกดรหัสสำหรับสิ่งนี้ แต่ฉันคิดว่าคุณสามารถรวบรวมความคิด

นอกจากนี้ฉันแน่ใจว่าคุณจะพบโซลูชันอื่น ๆ ถ้าคุณใช้ Google ในการ "Boggle Solver"



3

เฮฮา. ฉันเกือบโพสต์คำถามเดียวกันไม่กี่วันที่ผ่านมาเนื่องจากเกมด่าเดียวกัน! ฉันไม่ได้เพราะเพียงแค่ค้นหา google สำหรับหลามนักเกรงกลัวและได้รับคำตอบทั้งหมดที่ฉันต้องการ


ฉันไม่ทราบชื่อยอดนิยมของมันคือ "เกรงกลัว" แต่ฉันพบบางสิ่งใน google ฉันแค่อยากรู้ว่าคนอื่น ๆ จะคิดอะไรใน SO :)
เปาโล Bergantino

3

ฉันรู้ว่าเวลาของคำถามนี้มาถึงแล้ว แต่เนื่องจากฉันทำงานเพื่อแก้ปัญหาของตัวเองและสะดุดในขณะที่ googling เกี่ยวกับฉันคิดว่าฉันควรโพสต์การอ้างอิงกับฉันเพราะดูเหมือนว่าแตกต่างจากคนอื่น ๆ

ฉันเลือกที่จะไปกับเกมกระดานและทำการล่าซ้ำจากตัวอักษรแต่ละตัวบนกระดานจากเพื่อนบ้านที่ถูกต้องไปยังเพื่อนบ้านที่ถูกต้องขยายการล่าสัตว์หากรายการตัวอักษรปัจจุบันหากคำนำหน้าถูกต้องในดัชนี ในขณะที่การข้ามความคิดของคำปัจจุบันคือรายการของดัชนีเข้าสู่บอร์ดไม่ใช่ตัวอักษรที่ประกอบขึ้นเป็นคำ เมื่อตรวจสอบดัชนีดัชนีจะถูกแปลเป็นตัวอักษรและการตรวจสอบเสร็จสิ้น

ดัชนีนี้เป็นพจนานุกรมบังคับเดรัจฉานที่มีลักษณะเหมือนไทรอัม แต่สามารถสอบถาม Pythonic ของดัชนีได้ หากคำว่า 'cat' และ 'cater' อยู่ในรายการคุณจะได้รับสิ่งนี้ในพจนานุกรม:

   d = { 'c': ['cat','cater'],
     'ca': ['cat','cater'],
     'cat': ['cat','cater'],
     'cate': ['cater'],
     'cater': ['cater'],
   }

ดังนั้นถ้า current_word คือ 'ca' คุณรู้ว่ามันเป็นคำนำหน้าที่ถูกต้องเพราะ'ca' in dผลตอบแทนจริง (ดังนั้นดำเนินการสำรวจเส้นทางต่อบอร์ด) และถ้า current_word คือ 'cat' คุณก็รู้ว่ามันเป็นคำที่ถูกต้องเพราะมันเป็นคำนำหน้าที่ถูกต้องและ'cat' in d['cat']ส่งกลับ True เช่นกัน

หากรู้สึกเช่นนี้อนุญาตให้ใช้กับโค้ดที่อ่านได้บางอันซึ่งไม่ได้ดูช้าเกินไป เช่นเดียวกับคนอื่น ๆ ค่าใช้จ่ายในระบบนี้คือการอ่าน / สร้างดัชนี การแก้บอร์ดมีเสียงค่อนข้างดัง

รหัสที่http://gist.github.com/268079 มันเป็นแนวดิ่งและไร้เดียงสาที่มีการตรวจสอบความถูกต้องชัดเจนเพราะฉันต้องการเข้าใจปัญหาโดยไม่ต้อง crufting ขึ้นด้วยเวทมนตร์หรือความสับสน


3

ฉันเขียนตัวแก้ของฉันใน C ++ ฉันใช้โครงสร้างต้นไม้แบบกำหนดเอง ฉันไม่แน่ใจว่ามันสามารถถือเป็นคู่ชีวิตได้ แต่ก็คล้ายกัน แต่ละโหนดมี 26 สาขา 1 สำหรับแต่ละตัวอักษรของตัวอักษร ฉันไปตามกิ่งก้านของกระดานเกรงขามขนานกับกิ่งก้านของพจนานุกรมของฉัน หากไม่มีสาขาในพจนานุกรมฉันหยุดค้นหาบนกระดานเกรงกลัว ฉันแปลงตัวอักษรทั้งหมดบนกระดานเป็น ints ดังนั้น 'A' = 0 เนื่องจากเป็นเพียงอาร์เรย์ดังนั้นการค้นหาจึงเป็น O (1) เสมอ แต่ละโหนดจะเก็บถ้ามันเสร็จสมบูรณ์คำและจำนวนคำที่มีอยู่ในลูกของมัน ต้นไม้ถูกตัดแต่งเมื่อพบคำเพื่อลดการค้นหาคำเดียวกันซ้ำ ๆ ฉันเชื่อว่าการตัดแต่งกิ่งเป็น O (1)

CPU: Pentium SU2700 1.3GHz
RAM:

โหลดพจนานุกรม 178,590 คำใน <1 วินาที
แก้ปัญหา 100x100 Boggle (boggle.txt) ใน 4 วินาที พบประมาณ 44,000 คำ
การแก้ 4x4 Boggle นั้นเร็วเกินไปที่จะสร้างเกณฑ์มาตรฐานที่มีความหมาย :)

แก้ Boggle อย่างรวดเร็ว GitHub Repo


2

รับบอร์ดเกรงกลัวที่มี N แถวและคอลัมน์ M ลองสมมติสิ่งต่อไปนี้:

  • N * M มากกว่าจำนวนคำที่เป็นไปได้
  • N * M นั้นยิ่งใหญ่กว่าคำที่ยาวที่สุดที่เป็นไปได้

ภายใต้สมมติฐานเหล่านี้ความซับซ้อนของการแก้ปัญหานี้คือ O (N * M)

ฉันคิดว่าการเปรียบเทียบเวลาทำงานสำหรับบอร์ดตัวอย่างนี้หายไปหลายจุด แต่เพื่อความสมบูรณ์แบบโซลูชันนี้เสร็จสมบูรณ์ใน <0.2s บน MacBook Pro ที่ทันสมัยของฉัน

วิธีนี้จะค้นหาเส้นทางที่เป็นไปได้ทั้งหมดสำหรับแต่ละคำในคลังข้อมูล

#!/usr/bin/env ruby
# Example usage: ./boggle-solver --board "fxie amlo ewbx astu"

autoload :Matrix, 'matrix'
autoload :OptionParser, 'optparse'

DEFAULT_CORPUS_PATH = '/usr/share/dict/words'.freeze

# Functions

def filter_corpus(matrix, corpus, min_word_length)
  board_char_counts = Hash.new(0)
  matrix.each { |c| board_char_counts[c] += 1 }

  max_word_length = matrix.row_count * matrix.column_count
  boggleable_regex = /^[#{board_char_counts.keys.reduce(:+)}]{#{min_word_length},#{max_word_length}}$/
  corpus.select{ |w| w.match boggleable_regex }.select do |w|
    word_char_counts = Hash.new(0)
    w.each_char { |c| word_char_counts[c] += 1 }
    word_char_counts.all? { |c, count| board_char_counts[c] >= count }
  end
end

def neighbors(point, matrix)
  i, j = point
  ([i-1, 0].max .. [i+1, matrix.row_count-1].min).inject([]) do |r, new_i|
    ([j-1, 0].max .. [j+1, matrix.column_count-1].min).inject(r) do |r, new_j|
      neighbor = [new_i, new_j]
      neighbor.eql?(point) ? r : r << neighbor
    end
  end
end

def expand_path(path, word, matrix)
  return [path] if path.length == word.length

  next_char = word[path.length]
  viable_neighbors = neighbors(path[-1], matrix).select do |point|
    !path.include?(point) && matrix.element(*point).eql?(next_char)
  end

  viable_neighbors.inject([]) do |result, point|
    result + expand_path(path.dup << point, word, matrix)
  end
end

def find_paths(word, matrix)
  result = []
  matrix.each_with_index do |c, i, j|
    result += expand_path([[i, j]], word, matrix) if c.eql?(word[0])
  end
  result
end

def solve(matrix, corpus, min_word_length: 3)
  boggleable_corpus = filter_corpus(matrix, corpus, min_word_length)
  boggleable_corpus.inject({}) do |result, w|
    paths = find_paths(w, matrix)
    result[w] = paths unless paths.empty?
    result
  end
end

# Script

options = { corpus_path: DEFAULT_CORPUS_PATH }
option_parser = OptionParser.new do |opts|
  opts.banner = 'Usage: boggle-solver --board <value> [--corpus <value>]'

  opts.on('--board BOARD', String, 'The board (e.g. "fxi aml ewb ast")') do |b|
    options[:board] = b
  end

  opts.on('--corpus CORPUS_PATH', String, 'Corpus file path') do |c|
    options[:corpus_path] = c
  end

  opts.on_tail('-h', '--help', 'Shows usage') do
    STDOUT.puts opts
    exit
  end
end
option_parser.parse!

unless options[:board]
  STDERR.puts option_parser
  exit false
end

unless File.file? options[:corpus_path]
  STDERR.puts "No corpus exists - #{options[:corpus_path]}"
  exit false
end

rows = options[:board].downcase.scan(/\S+/).map{ |row| row.scan(/./) }

raw_corpus = File.readlines(options[:corpus_path])
corpus = raw_corpus.map{ |w| w.downcase.rstrip }.uniq.sort

solution = solve(Matrix.rows(rows), corpus)
solution.each_pair do |w, paths|
  STDOUT.puts w
  paths.each do |path|
    STDOUT.puts "\t" + path.map{ |point| point.inspect }.join(', ')
  end
end
STDOUT.puts "TOTAL: #{solution.count}"

2

วิธีการแก้ปัญหานี้ยังให้ทิศทางในการค้นหาในบอร์ดที่กำหนด

อัลโก:

1. Uses trie to save all the word in the english to fasten the search
2. The uses DFS to search the words in Boggle

เอาท์พุท:

Found "pic" directions from (4,0)(p) go  → →
Found "pick" directions from (4,0)(p) go  → → ↑
Found "pickman" directions from (4,0)(p) go  → → ↑ ↑ ↖ ↑
Found "picket" directions from (4,0)(p) go  → → ↑ ↗ ↖
Found "picked" directions from (4,0)(p) go  → → ↑ ↗ ↘
Found "pickle" directions from (4,0)(p) go  → → ↑ ↘ →

รหัส:

from collections import defaultdict
from nltk.corpus import words
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

english_words = words.words()

# If you wan to remove stop words
# stop_words = set(stopwords.words('english'))
# english_words = [w for w in english_words if w not in stop_words]

boggle = [
    ['c', 'n', 't', 's', 's'],
    ['d', 'a', 't', 'i', 'n'],
    ['o', 'o', 'm', 'e', 'l'],
    ['s', 'i', 'k', 'n', 'd'],
    ['p', 'i', 'c', 'l', 'e']
]

# Instead of X and Y co-ordinates
# better to use Row and column
lenc = len(boggle[0])
lenr = len(boggle)

# Initialize trie datastructure
trie_node = {'valid': False, 'next': {}}

# lets get the delta to find all the nighbors
neighbors_delta = [
    (-1,-1, "↖"),
    (-1, 0, "↑"),
    (-1, 1, "↗"),
    (0, -1, "←"),
    (0,  1, "→"),
    (1, -1, "↙"),
    (1,  0, "↓"),
    (1,  1, "↘"),
]


def gen_trie(word, node):
    """udpates the trie datastructure using the given word"""
    if not word:
        return

    if word[0] not in node:
        node[word[0]] = {'valid': len(word) == 1, 'next': {}}

    # recursively build trie
    gen_trie(word[1:], node[word[0]])


def build_trie(words, trie):
    """Builds trie data structure from the list of words given"""
    for word in words:
        gen_trie(word, trie)
    return trie


def get_neighbors(r, c):
    """Returns the neighbors for a given co-ordinates"""
    n = []
    for neigh in neighbors_delta:
        new_r = r + neigh[0]
        new_c = c + neigh[1]

        if (new_r >= lenr) or (new_c >= lenc) or (new_r < 0) or (new_c < 0):
            continue
        n.append((new_r, new_c, neigh[2]))
    return n


def dfs(r, c, visited, trie, now_word, direction):
    """Scan the graph using DFS"""
    if (r, c) in visited:
        return

    letter = boggle[r][c]
    visited.append((r, c))

    if letter in trie:
        now_word += letter

        if trie[letter]['valid']:
            print('Found "{}" {}'.format(now_word, direction))

        neighbors = get_neighbors(r, c)
        for n in neighbors:
            dfs(n[0], n[1], visited[::], trie[letter], now_word, direction + " " + n[2])


def main(trie_node):
    """Initiate the search for words in boggle"""
    trie_node = build_trie(english_words, trie_node)

    # print the board
    print("Given board")
    for i in range(lenr):print (boggle[i])
    print ('\n')

    for r in range(lenr):
        for c in range(lenc):
            letter = boggle[r][c]
            dfs(r, c, [], trie_node, '', 'directions from ({},{})({}) go '.format(r, c, letter))


if __name__ == '__main__':
    main(trie_node)

1

ผมได้ดำเนินการแก้ปัญหาใน OCaml มันรวบรวมพจนานุกรมเป็นคู่ชีวิตล่วงหน้าและใช้ความถี่ลำดับสองตัวอักษรเพื่อกำจัดขอบที่ไม่สามารถปรากฏเป็นคำเพื่อเร่งการประมวลผลได้เร็วขึ้น

มันแก้ไขบอร์ดตัวอย่างของคุณใน 0.35ms (พร้อมเวลาเริ่มต้น 6ms เพิ่มเติมซึ่งส่วนใหญ่เกี่ยวข้องกับการโหลด trie ลงในหน่วยความจำ)

วิธีแก้ปัญหาที่พบ:

["swami"; "emile"; "limbs"; "limbo"; "limes"; "amble"; "tubs"; "stub";
 "swam"; "semi"; "seam"; "awes"; "buts"; "bole"; "boil"; "west"; "east";
 "emil"; "lobs"; "limb"; "lime"; "lima"; "mesa"; "mews"; "mewl"; "maws";
 "milo"; "mile"; "awes"; "amie"; "axle"; "elma"; "fame"; "ubs"; "tux"; "tub";
 "twa"; "twa"; "stu"; "saw"; "sea"; "sew"; "sea"; "awe"; "awl"; "but"; "btu";
 "box"; "bmw"; "was"; "wax"; "oil"; "lox"; "lob"; "leo"; "lei"; "lie"; "mes";
 "mew"; "mae"; "maw"; "max"; "mil"; "mix"; "awe"; "awl"; "elm"; "eli"; "fax"]

นี่เป็นสิ่งที่ดี แต่ทุกครั้งที่โพสต์ที่นี่เกี่ยวข้องกับเวลา "เริ่มต้น" เพื่อโหลดพจนานุกรมลงในหน่วยความจำดังนั้นการเปรียบเทียบ 0.35 กับเวลาอื่น ๆ นั้นค่อนข้างไกลจากความแม่นยำ นอกจากนี้คุณใช้พจนานุกรมที่แตกต่างกันหรือไม่? คุณหายไปบางคำ ทั้งสองวิธี +1
เปาโลเบอร์กันติโน

เวลาเริ่มต้นใช้เวลา 6ms ดังนั้นคุณจึงดูที่ 6.35ms เพื่อการวิ่งที่สมบูรณ์ ฉันใช้/usr/share/dictพจนานุกรมท้องถิ่นของฉันและบางคำหายไป (เช่น EMBOLE)
Victor Nicollet

1

โซลูชัน Node.JS JavaScript คำนวณคำที่ไม่ซ้ำกันทั้งหมด 100 คำในเวลาน้อยกว่าหนึ่งวินาทีซึ่งรวมถึงไฟล์พจนานุกรมการอ่าน (MBA 2012)

เอาท์พุท:
["FAM", "TUX", "TUB", "FAE", "ELI", "ELM", "ELB", "TWA", "TWA", "TWA", "SAW", "AMI", "SWA" "SWA", "AME", "SEA", "SEW", "AES", "สว่าน", "กลัว", "SEA", "AWA", "MIX", "MIL", "เอเอสที"," ASE", "แม็กซ์", "แม่", "กระเพาะปลา", "มิว", "กลัว", "MES", "สว่าน", "โกหก", "ลิม", "AWA", "AES", "แต่" "BLO" "ถูก", "WAE", "WEA", "LEI", "ลีโอ", "ลอบ", "แซลมอนรมควัน", "WEM", "ออยล์", "OLM", "WEA"," WAE", "แว็กซ์", "WAF""ไมโล", "EAST", "WAME", "TWAS", "TWAE", "EMIL", "weam", "OIME", "รักแร้", "WEST", "TWAE", "LIMB", "ป๊ ", "เคย", "BLEO", "STUB", "เดือด", "Bole", "มะนาว", "SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME" "อาเซม", "MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST ", "AWEST", "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]EAST", "WAME", "TWAS", "TWAE", "EMIL", "weam", "OIME", "รักแร้", "WEST", "TWAE", "LIMB", "ป๊", "เจ้า" "BLEO", "STUB", "เดือด", "Bole", "มะนาว", "SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม"," MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST" "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]EAST", "WAME", "TWAS", "TWAE", "EMIL", "weam", "OIME", "รักแร้", "WEST", "TWAE", "LIMB", "ป๊", "เจ้า" "BLEO", "STUB", "เดือด", "Bole", "มะนาว", "SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม"," MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST" "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]"TWAE", "EMIL", "weam", "OIME", "รักแร้", "WEST", "TWAE", "LIMB", "ป๊", "เคย", "BLEO", "STUB", "เดือด ", "Bole", "มะนาว", "SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX" "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu ", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]"TWAE", "EMIL", "weam", "OIME", "รักแร้", "WEST", "TWAE", "LIMB", "ป๊", "เคย", "BLEO", "STUB", "เดือด ", "Bole", "มะนาว", "SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX" "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu ", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]"WEST", "TWAE", "LIMB", "ป๊", "เคย", "BLEO", "STUB", "เดือด", "Bole", "มะนาว", "SAWT", "LIMA", "MESA ", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI" "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE ", "FAMBLE"]"WEST", "TWAE", "LIMB", "ป๊", "เคย", "BLEO", "STUB", "เดือด", "Bole", "มะนาว", "SAWT", "LIMA", "MESA ", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ", "Ambo", "AMLI" "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE ", "FAMBLE"]SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ" "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX"," semble", "EMBOLE", "WAMBLE", "FAMBLE"]SAWT", "LIMA", "MESA", "ออกเสี่ยงแมว", "AXLE", "FAME", "อาเซม", "MILE", "AMIL", "SEAX", "รอยต่อ", "SEMI", "ว่ายน้ำ" "Ambo", "AMLI", "Axile", "เดินทอดน่อง", "สวามี", "AWEST", "AWEST", "Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX"," semble", "EMBOLE", "WAMBLE", "FAMBLE"]Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]Limax", "มะนาว", "Limbu", "LIMBO", "EMBOX", "semble", "EMBOLE", "WAMBLE", "FAMBLE"]

รหัส:

var fs = require('fs')

var Node = function(value, row, col) {
    this.value = value
    this.row = row
    this.col = col
}

var Path = function() {
    this.nodes = []
}

Path.prototype.push = function(node) {
    this.nodes.push(node)
    return this
}

Path.prototype.contains = function(node) {
    for (var i = 0, ii = this.nodes.length; i < ii; i++) {
        if (this.nodes[i] === node) {
            return true
        }
    }

    return false
}

Path.prototype.clone = function() {
    var path = new Path()
    path.nodes = this.nodes.slice(0)
    return path
}

Path.prototype.to_word = function() {
    var word = ''

    for (var i = 0, ii = this.nodes.length; i < ii; ++i) {
        word += this.nodes[i].value
    }

    return word
}

var Board = function(nodes, dict) {
    // Expects n x m array.
    this.nodes = nodes
    this.words = []
    this.row_count = nodes.length
    this.col_count = nodes[0].length
    this.dict = dict
}

Board.from_raw = function(board, dict) {
    var ROW_COUNT = board.length
      , COL_COUNT = board[0].length

    var nodes = []

    // Replace board with Nodes
    for (var i = 0, ii = ROW_COUNT; i < ii; ++i) {
        nodes.push([])
        for (var j = 0, jj = COL_COUNT; j < jj; ++j) {
            nodes[i].push(new Node(board[i][j], i, j))
        }
    }

    return new Board(nodes, dict)
}

Board.prototype.toString = function() {
    return JSON.stringify(this.nodes)
}

Board.prototype.update_potential_words = function(dict) {
    for (var i = 0, ii = this.row_count; i < ii; ++i) {
        for (var j = 0, jj = this.col_count; j < jj; ++j) {
            var node = this.nodes[i][j]
              , path = new Path()

            path.push(node)

            this.dfs_search(path)
        }
    }
}

Board.prototype.on_board = function(row, col) {
    return 0 <= row && row < this.row_count && 0 <= col && col < this.col_count
}

Board.prototype.get_unsearched_neighbours = function(path) {
    var last_node = path.nodes[path.nodes.length - 1]

    var offsets = [
        [-1, -1], [-1,  0], [-1, +1]
      , [ 0, -1],           [ 0, +1]
      , [+1, -1], [+1,  0], [+1, +1]
    ]

    var neighbours = []

    for (var i = 0, ii = offsets.length; i < ii; ++i) {
        var offset = offsets[i]
        if (this.on_board(last_node.row + offset[0], last_node.col + offset[1])) {

            var potential_node = this.nodes[last_node.row + offset[0]][last_node.col + offset[1]]
            if (!path.contains(potential_node)) {
                // Create a new path if on board and we haven't visited this node yet.
                neighbours.push(potential_node)
            }
        }
    }

    return neighbours
}

Board.prototype.dfs_search = function(path) {
    var path_word = path.to_word()

    if (this.dict.contains_exact(path_word) && path_word.length >= 3) {
        this.words.push(path_word)
    }

    var neighbours = this.get_unsearched_neighbours(path)

    for (var i = 0, ii = neighbours.length; i < ii; ++i) {
        var neighbour = neighbours[i]
        var new_path = path.clone()
        new_path.push(neighbour)

        if (this.dict.contains_prefix(new_path.to_word())) {
            this.dfs_search(new_path)
        }
    }
}

var Dict = function() {
    this.dict_array = []

    var dict_data = fs.readFileSync('./web2', 'utf8')
    var dict_array = dict_data.split('\n')

    for (var i = 0, ii = dict_array.length; i < ii; ++i) {
        dict_array[i] = dict_array[i].toUpperCase()
    }

    this.dict_array = dict_array.sort()
}

Dict.prototype.contains_prefix = function(prefix) {
    // Binary search
    return this.search_prefix(prefix, 0, this.dict_array.length)
}

Dict.prototype.contains_exact = function(exact) {
    // Binary search
    return this.search_exact(exact, 0, this.dict_array.length)
}

Dict.prototype.search_prefix = function(prefix, start, end) {
    if (start >= end) {
        // If no more place to search, return no matter what.
        return this.dict_array[start].indexOf(prefix) > -1
    }

    var middle = Math.floor((start + end)/2)

    if (this.dict_array[middle].indexOf(prefix) > -1) {
        // If we prefix exists, return true.
        return true
    } else {
        // Recurse
        if (prefix <= this.dict_array[middle]) {
            return this.search_prefix(prefix, start, middle - 1)
        } else {
            return this.search_prefix(prefix, middle + 1, end)
        }
    }
}

Dict.prototype.search_exact = function(exact, start, end) {
    if (start >= end) {
        // If no more place to search, return no matter what.
        return this.dict_array[start] === exact
    }

    var middle = Math.floor((start + end)/2)

    if (this.dict_array[middle] === exact) {
        // If we prefix exists, return true.
        return true
    } else {
        // Recurse
        if (exact <= this.dict_array[middle]) {
            return this.search_exact(exact, start, middle - 1)
        } else {
            return this.search_exact(exact, middle + 1, end)
        }
    }
}

var board = [
    ['F', 'X', 'I', 'E']
  , ['A', 'M', 'L', 'O']
  , ['E', 'W', 'B', 'X']
  , ['A', 'S', 'T', 'U']
]

var dict = new Dict()

var b = Board.from_raw(board, dict)
b.update_potential_words()
console.log(JSON.stringify(b.words.sort(function(a, b) {
    return a.length - b.length
})))

1

ดังนั้นฉันจึงต้องการเพิ่มอีกวิธีหนึ่งของการแก้ปัญหา PHP เนื่องจากทุกคนชอบ PHP มีการรีแฟคเตอร์เล็กน้อยที่ฉันต้องการเช่นใช้ regexpression จับคู่กับไฟล์พจนานุกรม แต่ตอนนี้ฉันแค่โหลดไฟล์พจนานุกรมทั้งหมดลงใน wordList

ฉันทำสิ่งนี้โดยใช้แนวคิดรายการที่ลิงก์ แต่ละโหนดมีค่าอักขระค่าตำแหน่งและตัวชี้ถัดไป

ค่าที่ตั้งเป็นวิธีที่ฉันค้นพบว่ามีการเชื่อมต่อสองโหนด

1     2     3     4
11    12    13    14
21    22    23    24
31    32    33    34

ดังนั้นการใช้กริดนั้นฉันรู้ว่าสองโหนดเชื่อมต่อกันถ้าตำแหน่งของโหนดแรกเท่ากับตำแหน่งที่สองของโหนด +/- 1 สำหรับแถวเดียวกัน +/- 9, 10, 11 สำหรับแถวด้านบนและด้านล่าง

ฉันใช้การสอบถามซ้ำสำหรับการค้นหาหลัก ใช้คำที่ปิด wordList ค้นหาจุดเริ่มต้นที่เป็นไปได้ทั้งหมดจากนั้นค้นหาการเชื่อมต่อที่เป็นไปได้แบบวนซ้ำโดยคำนึงว่ามันไม่สามารถไปยังตำแหน่งที่ใช้งานอยู่ได้

อย่างไรก็ตามฉันรู้ว่ามันต้องการการรีแฟคเจอริ่งบ้างและชอบที่จะได้ยินความคิดเกี่ยวกับวิธีทำให้มันสะอาดขึ้น แต่มันก็ให้ผลลัพธ์ที่ถูกต้องตามไฟล์พจนานุกรมที่ฉันใช้ ขึ้นอยู่กับจำนวนของเสียงสระและการรวมกันบนกระดานใช้เวลาประมาณ 3 ถึง 6 วินาที ฉันรู้ว่าเมื่อฉัน preg_match ผลลัพธ์ของพจนานุกรมนั้นจะลดลงอย่างมาก

<?php
    ini_set('xdebug.var_display_max_depth', 20);
    ini_set('xdebug.var_display_max_children', 1024);
    ini_set('xdebug.var_display_max_data', 1024);

    class Node {
        var $loc;

        function __construct($value) {
            $this->value = $value;
            $next = null;
        }
    }

    class Boggle {
        var $root;
        var $locList = array (1, 2, 3, 4, 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34);
        var $wordList = [];
        var $foundWords = [];

        function __construct($board) {
            // Takes in a board string and creates all the nodes
            $node = new Node($board[0]);
            $node->loc = $this->locList[0];
            $this->root = $node;
            for ($i = 1; $i < strlen($board); $i++) {
                    $node->next = new Node($board[$i]);
                    $node->next->loc = $this->locList[$i];
                    $node = $node->next;
            }
            // Load in a dictionary file
            // Use regexp to elimate all the words that could never appear and load the 
            // rest of the words into wordList
            $handle = fopen("dict.txt", "r");
            if ($handle) {
                while (($line = fgets($handle)) !== false) {
                    // process the line read.
                    $line = trim($line);
                    if (strlen($line) > 2) {
                        $this->wordList[] = trim($line);
                    }
                }
                fclose($handle);
            } else {
                // error opening the file.
                echo "Problem with the file.";
            } 
        }

        function isConnected($node1, $node2) {
        // Determines if 2 nodes are connected on the boggle board

            return (($node1->loc == $node2->loc + 1) || ($node1->loc == $node2->loc - 1) ||
               ($node1->loc == $node2->loc - 9) || ($node1->loc == $node2->loc - 10) || ($node1->loc == $node2->loc - 11) ||
               ($node1->loc == $node2->loc + 9) || ($node1->loc == $node2->loc + 10) || ($node1->loc == $node2->loc + 11)) ? true : false;

        }

        function find($value, $notInLoc = []) {
            // Returns a node with the value that isn't in a location
            $current = $this->root;
            while($current) {
                if ($current->value == $value && !in_array($current->loc, $notInLoc)) {
                    return $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return false;
        }

        function findAll($value) {
            // Returns an array of nodes with a specific value
            $current = $this->root;
            $foundNodes = [];
            while ($current) {
                if ($current->value == $value) {
                    $foundNodes[] = $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return (empty($foundNodes)) ? false : $foundNodes;
        }

        function findAllConnectedTo($node, $value, $notInLoc = []) {
            // Returns an array of nodes that are connected to a specific node and 
            // contain a specific value and are not in a certain location
            $nodeList = $this->findAll($value);
            $newList = [];
            if ($nodeList) {
                foreach ($nodeList as $node2) {
                    if (!in_array($node2->loc, $notInLoc) && $this->isConnected($node, $node2)) {
                        $newList[] = $node2;
                    }
                }
            }
            return (empty($newList)) ? false : $newList;
        }



        function inner($word, $list, $i = 0, $notInLoc = []) {
            $i++;
            foreach($list as $node) {
                $notInLoc[] = $node->loc;
                if ($list2 = $this->findAllConnectedTo($node, $word[$i], $notInLoc)) {
                    if ($i == (strlen($word) - 1)) {
                        return true;
                    } else {
                        return $this->inner($word, $list2, $i, $notInLoc);
                    }
                }
            }
            return false;
        }

        function findWord($word) {
            if ($list = $this->findAll($word[0])) {
                return $this->inner($word, $list);
            }
            return false;
        }

        function findAllWords() {
            foreach($this->wordList as $word) {
                if ($this->findWord($word)) {
                    $this->foundWords[] = $word;
                }
            }
        }

        function displayBoard() {
            $current = $this->root;
            for ($i=0; $i < 4; $i++) {
                echo $current->value . " " . $current->next->value . " " . $current->next->next->value . " " . $current->next->next->next->value . "<br />";
                if ($i < 3) {
                    $current = $current->next->next->next->next;
                }
            }
        }

    }

    function randomBoardString() {
        return substr(str_shuffle(str_repeat("abcdefghijklmnopqrstuvwxyz", 16)), 0, 16);
    }

    $myBoggle = new Boggle(randomBoardString());
    $myBoggle->displayBoard();
    $x = microtime(true);
    $myBoggle->findAllWords();
    $y = microtime(true);
    echo ($y-$x);
    var_dump($myBoggle->foundWords);

    ?>

1

ฉันรู้ว่าฉันมางานสายจริงๆ แต่ฉันได้นำไปใช้เป็นแบบฝึกหัดการเขียนรหัสตัวแก้ปัญหาในภาษาโปรแกรมหลายภาษา (C ++, Java, Go, C #, Python, Ruby, JavaScript, Julia, Lua, PHP, Perl) และ ฉันคิดว่าบางคนอาจสนใจสิ่งเหล่านั้นดังนั้นฉันจึงทิ้งลิงค์ไว้ที่นี่: https://github.com/AmokHuginnsson/boggle-solvers


1

นี่คือวิธีแก้ปัญหาการใช้คำที่กำหนดไว้ล่วงหน้าในชุดเครื่องมือ NLTK NLTK มีแพ็คเกจ nltk.corpus ซึ่งเรามีแพคเกจที่เรียกว่า Words และมีคำภาษาอังกฤษมากกว่า 2Lakhs ที่คุณสามารถใช้ทั้งหมดในโปรแกรมของคุณ

เมื่อสร้างเมทริกซ์ของคุณจะแปลงเป็นอาเรย์ตัวละครและทำรหัสนี้

import nltk
from nltk.corpus import words
from collections import Counter

def possibleWords(input, charSet):
    for word in input:
        dict = Counter(word)
        flag = 1
        for key in dict.keys():
            if key not in charSet:
                flag = 0
        if flag == 1 and len(word)>5: #its depends if you want only length more than 5 use this otherwise remove that one. 
            print(word)


nltk.download('words')
word_list = words.words()
# prints 236736
print(len(word_list))
charSet = ['h', 'e', 'l', 'o', 'n', 'v', 't']
possibleWords(word_list, charSet)

เอาท์พุท:

eleven
eleventh
elevon
entente
entone
ethene
ethenol
evolve
evolvent
hellhole
helvell
hooven
letten
looten
nettle
nonene
nonent
nonlevel
notelet
novelet
novelette
novene
teenet
teethe
teevee
telethon
tellee
tenent
tentlet
theelol
toetoe
tonlet
toothlet
tootle
tottle
vellon
velvet
velveteen
venene
vennel
venthole
voeten
volent
volvelle
volvent
voteen

ฉันหวังว่าคุณจะได้รับมัน


0

นี่คือการใช้ java ของฉัน: https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java

Trie build ใช้เวลา 0 ชั่วโมง 0 นาที 1 วินาที 532 มิลลิวินาที
การค้นหาคำใช้เวลา 0 ชั่วโมง 0 นาที 0 วินาที 92 วินาที

eel eeler eely eer eke eker eld eleut elk ell 
elle epee epihippus ere erept err error erupt eurus eye 
eyer eyey hip hipe hiper hippish hipple hippus his hish 
hiss hist hler hsi ihi iphis isis issue issuer ist 
isurus kee keek keeker keel keeler keep keeper keld kele 
kelek kelep kelk kell kelly kelp kelper kep kepi kept 
ker kerel kern keup keuper key kyl kyle lee leek 
leeky leep leer lek leo leper leptus lepus ler leu 
ley lleu lue lull luller lulu lunn lunt lunule luo 
lupe lupis lupulus lupus lur lure lurer lush lushly lust 
lustrous lut lye nul null nun nupe nurture nurturer nut 
oer ore ort ouphish our oust out outpeep outpeer outpipe 
outpull outpush output outre outrun outrush outspell outspue outspurn outspurt 
outstrut outstunt outsulk outturn outusure oyer pee peek peel peele 
peeler peeoy peep peeper peepeye peer pele peleus pell peller 
pelu pep peplus pepper pepperer pepsis per pern pert pertussis 
peru perule perun peul phi pip pipe piper pipi pipistrel 
pipistrelle pipistrellus pipper pish piss pist plup plus plush ply 
plyer psi pst puerer pul pule puler pulk pull puller 
pulley pullus pulp pulper pulu puly pun punt pup puppis 
pur pure puree purely purer purr purre purree purrel purrer 
puru purupuru pus push puss pustule put putt puture ree 
reek reeker reeky reel reeler reeper rel rely reoutput rep 
repel repeller repipe reply repp reps reree rereel rerun reuel 
roe roer roey roue rouelle roun roup rouper roust rout 
roy rue ruelle ruer rule ruler rull ruller run runt 
rupee rupert rupture ruru rus rush russ rust rustre rut 
shi shih ship shipper shish shlu sip sipe siper sipper 
sis sish sisi siss sissu sist sistrurus speel speer spelk 
spell speller splurt spun spur spurn spurrer spurt sput ssi 
ssu stre stree streek streel streeler streep streke streperous strepsis 
strey stroup stroy stroyer strue strunt strut stu stue stull 
stuller stun stunt stupe stupeous stupp sturnus sturt stuss stut 
sue suer suerre suld sulk sulker sulky sull sully sulu 
sun sunn sunt sunup sup supe super superoutput supper supple 
supplely supply sur sure surely surrey sus susi susu susurr 
susurrous susurrus sutu suture suu tree treey trek trekker trey 
troupe trouper trout troy true truer trull truller truly trun 
trush truss trust tshi tst tsun tsutsutsi tue tule tulle 
tulu tun tunu tup tupek tupi tur turn turnup turr 
turus tush tussis tussur tut tuts tutu tutulus ule ull 
uller ulu ululu unreel unrule unruly unrun unrust untrue untruly 
untruss untrust unturn unurn upper upperer uppish uppishly uppull uppush 
upspurt upsun upsup uptree uptruss upturn ure urn uro uru 
urus urushi ush ust usun usure usurer utu yee yeel 
yeld yelk yell yeller yelp yelper yeo yep yer yere 
yern yoe yor yore you youl youp your yourn yoy 

หมายเหตุ: ฉันใช้พจนานุกรมและเมทริกซ์อักขระที่จุดเริ่มต้นของเธรดนี้ รหัสถูกเรียกใช้บน MacBookPro ของฉันด้านล่างนี้คือข้อมูลบางอย่างเกี่ยวกับเครื่อง

ชื่อรุ่น: MacBook Pro
ตัวระบุรุ่น: MacBookPro8,1
ชื่อหน่วยประมวลผล: Intel Core i5
ความเร็วโปรเซสเซอร์: 2.3 GHz
จำนวนโปรเซสเซอร์: 1
จำนวนแกนทั้งหมด: 2
แคช L2 (ต่อคอร์): 256 KB
L3 แคช: 3 MB
หน่วยความจำ: 4 GB
Boot ROM เวอร์ชั่น: MBP81.0047.B0E
SMC เวอร์ชัน (ระบบ): 1.68f96


0

ฉันแก้ไขมันด้วย Java การติดตั้งของฉันมีความยาว 269 บรรทัดและใช้งานง่าย ก่อนอื่นคุณต้องสร้างอินสแตนซ์ใหม่ของคลาส Boggler จากนั้นเรียกใช้ฟังก์ชันแก้ปัญหาโดยใช้ตารางเป็นพารามิเตอร์ ใช้เวลาประมาณ 100 มิลลิวินาทีในการโหลดพจนานุกรม 50,000 คำบนคอมพิวเตอร์ของฉันและพบคำศัพท์ประมาณ 10-20 มิลลิวินาที พบคำที่เก็บไว้ใน foundWordsArrayList,

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class Boggler {
    private ArrayList<String> words = new ArrayList<String>();      
    private ArrayList<String> roundWords = new ArrayList<String>(); 
    private ArrayList<Word> foundWords = new ArrayList<Word>();     
    private char[][] letterGrid = new char[4][4];                   
    private String letters;                                         

    public Boggler() throws FileNotFoundException, IOException, URISyntaxException {
        long startTime = System.currentTimeMillis();

        URL path = GUI.class.getResource("words.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path.toURI()).getAbsolutePath()), "iso-8859-1"));
        String line;
        while((line = br.readLine()) != null) {
            if(line.length() < 3 || line.length() > 10) {
                continue;
            }

            this.words.add(line);
        }
    }

    public ArrayList<Word> getWords() {
        return this.foundWords;
    }

    public void solve(String letters) {
        this.letters = "";
        this.foundWords = new ArrayList<Word>();

        for(int i = 0; i < letters.length(); i++) {
            if(!this.letters.contains(letters.substring(i, i + 1))) {
                this.letters += letters.substring(i, i + 1);
            }
        }

        for(int i = 0; i < 4; i++) {
            for(int j = 0; j < 4; j++) {
                this.letterGrid[i][j] = letters.charAt(i * 4 + j);
            }
        }

        System.out.println(Arrays.deepToString(this.letterGrid));               

        this.roundWords = new ArrayList<String>();      
        String pattern = "[" + this.letters + "]+";     

        for(int i = 0; i < this.words.size(); i++) {

            if(this.words.get(i).matches(pattern)) {
                this.roundWords.add(this.words.get(i));
            }
        }

        for(int i = 0; i < this.roundWords.size(); i++) {
            Word word = checkForWord(this.roundWords.get(i));

            if(word != null) {
                System.out.println(word);
                this.foundWords.add(word);
            }
        }       
    }

    private Word checkForWord(String word) {
        char initial = word.charAt(0);
        ArrayList<LetterCoord> startPoints = new ArrayList<LetterCoord>();

        int x = 0;  
        int y = 0;
        for(char[] row: this.letterGrid) {
            x = 0;

            for(char letter: row) {
                if(initial == letter) {
                    startPoints.add(new LetterCoord(x, y));
                }

                x++;
            }

            y++;
        }

        ArrayList<LetterCoord> letterCoords = null;
        for(int initialTry = 0; initialTry < startPoints.size(); initialTry++) {
            letterCoords = new ArrayList<LetterCoord>();    

            x = startPoints.get(initialTry).getX(); 
            y = startPoints.get(initialTry).getY();

            LetterCoord initialCoord = new LetterCoord(x, y);
            letterCoords.add(initialCoord);

            letterLoop: for(int letterIndex = 1; letterIndex < word.length(); letterIndex++) {
                LetterCoord lastCoord = letterCoords.get(letterCoords.size() - 1);  
                char currentChar = word.charAt(letterIndex);                        

                ArrayList<LetterCoord> letterLocations = getNeighbours(currentChar, lastCoord.getX(), lastCoord.getY());

                if(letterLocations == null) {
                    return null;    
                }       

                for(int foundIndex = 0; foundIndex < letterLocations.size(); foundIndex++) {
                    if(letterIndex != word.length() - 1 && true == false) {
                        char nextChar = word.charAt(letterIndex + 1);
                        int lastX = letterCoords.get(letterCoords.size() - 1).getX();
                        int lastY = letterCoords.get(letterCoords.size() - 1).getY();

                        ArrayList<LetterCoord> possibleIndex = getNeighbours(nextChar, lastX, lastY);
                        if(possibleIndex != null) {
                            if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                                letterCoords.add(letterLocations.get(foundIndex));
                            }
                            continue letterLoop;
                        } else {
                            return null;
                        }
                    } else {
                        if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                            letterCoords.add(letterLocations.get(foundIndex));

                            continue letterLoop;
                        }
                    }
                }
            }

            if(letterCoords != null) {
                if(letterCoords.size() == word.length()) {
                    Word w = new Word(word);
                    w.addList(letterCoords);
                    return w;
                } else {
                    return null;
                }
            }
        }

        if(letterCoords != null) {
            Word foundWord = new Word(word);
            foundWord.addList(letterCoords);

            return foundWord;
        }

        return null;
    }

    public ArrayList<LetterCoord> getNeighbours(char letterToSearch, int x, int y) {
        ArrayList<LetterCoord> neighbours = new ArrayList<LetterCoord>();

        for(int _y = y - 1; _y <= y + 1; _y++) {
            for(int _x = x - 1; _x <= x + 1; _x++) {
                if(_x < 0 || _y < 0 || (_x == x && _y == y) || _y > 3 || _x > 3) {
                    continue;
                }

                if(this.letterGrid[_y][_x] == letterToSearch && !neighbours.contains(new LetterCoord(_x, _y))) {
                    neighbours.add(new LetterCoord(_x, _y));
                }
            }
        }

        if(neighbours.isEmpty()) {
            return null;
        } else {
            return neighbours;
        }
    }
}

class Word {
    private String word;    
    private ArrayList<LetterCoord> letterCoords = new ArrayList<LetterCoord>();

    public Word(String word) {
        this.word = word;
    }

    public boolean addCoords(int x, int y) {
        LetterCoord lc = new LetterCoord(x, y);

        if(!this.letterCoords.contains(lc)) {
            this.letterCoords.add(lc);

            return true;
        }

        return false;
    }

    public void addList(ArrayList<LetterCoord> letterCoords) {
        this.letterCoords = letterCoords;
    } 

    @Override
    public String toString() {
        String outputString = this.word + " ";
        for(int i = 0; i < letterCoords.size(); i++) {
            outputString += "(" + letterCoords.get(i).getX() + ", " + letterCoords.get(i).getY() + ") ";
        }

        return outputString;
    }

    public String getWord() {
        return this.word;
    }

    public ArrayList<LetterCoord> getList() {
        return this.letterCoords;
    }
}

class LetterCoord extends ArrayList {
    private int x;          
    private int y;          

    public LetterCoord(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;
    }

    @Override
    public boolean equals(Object o) {
        if(!(o instanceof LetterCoord)) {
            return false;
        }

        LetterCoord lc = (LetterCoord) o;

        if(this.x == lc.getX() &&
                this.y == lc.getY()) {
            return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + this.x;
        hash = 24 * hash + this.y;
        return hash;
    }
}

0

ฉันแก้ไขสิ่งนี้ในค ใช้เวลาประมาณ 48 ms ในการเรียกใช้บนเครื่องของฉัน (โดยประมาณ 98% ของเวลาที่ใช้ในการโหลดพจนานุกรมจากดิสก์และสร้าง trie) พจนานุกรมคือ / usr / share / dict / american-english ซึ่งมี 62886 คำ

รหัสแหล่งที่มา


0

ฉันแก้ไขมันอย่างสมบูรณ์แบบและเร็วมาก ฉันใส่มันลงในแอพ Android ดูวิดีโอได้ที่ลิงก์ play store เพื่อดูการทำงาน

Word Cheats เป็นแอพที่ "แตก" เกมคำศัพท์สไตล์เมทริกซ์ แอพนี้ถูกสร้างขึ้นเพื่อช่วยให้ฉันโกงคำว่า scrambler มันสามารถใช้สำหรับการค้นหาคำ, ruzzle, คำ, โปรแกรมค้นหาคำ, crack word, เกรงกลัวและอื่น ๆ !

สามารถดูได้ที่นี่ https://play.google.com/store/apps/details?id=com.harris.wordcracker

ดูแอปที่กำลังใช้งานในวิดีโอ https://www.youtube.com/watch?v=DL2974WmNAI

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.