2014-02-06 21 views
5

我正試圖解決Boggle反向問題。簡單地說,給出一個單詞列表,提出一個4x4的字母表格,其中列表中的許多單詞可以在相鄰字母序列中找到(字母正交和對角地相鄰)。如何從單詞列表創建一個Boggle Board? (反向Boggle解算器!)

我不想拿一個已知的電路板並解決它。這是一個簡單的TRIE問題,在人們的CS項目中已經討論/解決了這個問題。

示例單詞列表:

margays, jaguars, cougars, tomcats, margay, jaguar, cougar, pumas, puma, toms 

解決方案:

ATJY 
CTSA 
OMGS 
PUAR 

這個問題確實很難(對我來說)。算法我到目前爲止:

  1. 對於輸入中的每個單詞,列出所有可能的方式,它可以合法地出現在板上。
  2. 嘗試在這些板上放置單詞#2的所有可能組合,並保留那些沒有衝突的組合。
  3. 重複至列表結尾。
  4. ...
  5. 利潤! (對於那些讀/)

很明顯,有實現細節。先從最長的單詞開始。忽略其他詞的子字串。

我可以在大約0.4秒內爲7個字符的單詞生成所有68k塊可能的電路板。然後,當我添加額外的7字符板時,我需要比較68k x 68k板x 7比較。解決時間變成冰河。

必須有更好的方法來做到這一點!

一些代碼:

BOARD_SIDE_LENGTH = 4 

class Board: 
    def __init__(self): 
     pass 

    def setup(self, word, start_position): 
     self.word = word 
     self.indexSequence = [start_position,] 
     self.letters_left_over = word[1:] 
     self.overlay = [] 
     # set up template for overlay. When we compare boards, we will add to this if the board fits 
     for i in range(BOARD_SIDE_LENGTH*BOARD_SIDE_LENGTH): 
      self.overlay.append('') 
     self.overlay[start_position] = word[0] 
     self.overlay_count = 0 

    @classmethod 
    def copy(boardClass, board): 
     newBoard = boardClass() 
     newBoard.word = board.word 
     newBoard.indexSequence = board.indexSequence[:] 
     newBoard.letters_left_over = board.letters_left_over 
     newBoard.overlay = board.overlay[:] 
     newBoard.overlay_count = board.overlay_count 
     return newBoard 

    # need to check if otherboard will fit into existing board (allowed to use blank spaces!) 
    # otherBoard will always be just a single word 
    @classmethod 
    def testOverlay(self, this_board, otherBoard): 
     for pos in otherBoard.indexSequence: 
      this_board_letter = this_board.overlay[pos] 
      other_board_letter = otherBoard.overlay[pos] 
      if this_board_letter == '' or other_board_letter == '': 
       continue 
      elif this_board_letter == other_board_letter: 
       continue 
      else: 
       return False 
     return True 

    @classmethod 
    def doOverlay(self, this_board, otherBoard): 
     # otherBoard will always be just a single word 
     for pos in otherBoard.indexSequence: 
      this_board.overlay[pos] = otherBoard.overlay[pos] 
     this_board.overlay_count = this_board.overlay_count + 1 

    @classmethod 
    def newFromBoard(boardClass, board, next_position): 
     newBoard = boardClass() 
     newBoard.indexSequence = board.indexSequence + [next_position] 
     newBoard.word = board.word 
     newBoard.overlay = board.overlay[:] 
     newBoard.overlay[next_position] = board.letters_left_over[0]  
     newBoard.letters_left_over = board.letters_left_over[1:] 
     newBoard.overlay_count = board.overlay_count 
     return newBoard 

    def getValidCoordinates(self, board, position): 
     row = position/4 
     column = position % 4 
     for r in range(row - 1, row + 2): 
      for c in range(column - 1, column + 2): 
       if r >= 0 and r < BOARD_SIDE_LENGTH and c >= 0 and c < BOARD_SIDE_LENGTH: 
        if (r*BOARD_SIDE_LENGTH+c not in board.indexSequence): 
         yield r, c 

class boardgen: 
    def __init__(self): 
     self.boards = [] 

    def createAll(self, board): 
     # get the next letter 
     if len(board.letters_left_over) == 0: 
      self.boards.append(board) 
      return 
     next_letter = board.letters_left_over[0]  
     last_position = board.indexSequence[-1] 
     for row, column in board.getValidCoordinates(board, last_position): 
      new_board = Board.newFromBoard(board, row*BOARD_SIDE_LENGTH+column) 
      self.createAll(new_board) 

而且使用這樣的:

words = ['margays', 'jaguars', 'cougars', 'tomcats', 'margay', 'jaguar', 'cougar', 'pumas', 'puma'] 
words.sort(key=len) 

first_word = words.pop() 

# generate all boards for the first word 
overlaid_boards = [] 
for i in range(BOARD_SIDE_LENGTH*BOARD_SIDE_LENGTH): 
    test_board = Board() 
    test_board.setup(first_word, i) 
    generator = boardgen() 
    generator.createAll(test_board) 
    overlaid_boards += generator.boards 
+0

「Boggle」的含義很簡單,也就是說,您是否考慮了實際的Boggle多維數據集(來自一些變體)?例如,Qu在實際遊戲中的立方體的單個面上,並且存在特定的字母分佈。 – ooga

+0

@ooga:http://en.wikipedia.org/wiki/Boggle,你可以忽略Qu和其他Digraphs的目的。信件分發僅基於提供的信件。 – Richard

+0

我想象你可以通過預先計算某些事情來節省大量的工作。只有68k板意味着您可以在字典中將通用詞的哈希值包含在獨特的棋盤狀態中。然後你的問題就變成了識別包含每個單詞的已知電路板,以及哪個電路板最經常出現。 –

回答

0

有加快原路返回搜索一對夫婦的總體思路,你可以嘗試:

1)早期檢查。通常有助於放棄不可能儘早發揮作用的部分解決方案,即使以更多工作爲代價。考慮通過切斷您嘗試適合的單詞產生的所有雙字符序列 - 例如PUMAS貢獻PU,UM,MA和AS。這些都必須出現在最終答案中。如果部分解決方案沒有足夠的重疊雙字符空格來容納所有重疊的雙字符序列,但它不能擴展到最終答案。

2)對稱性。我認爲這可能是最有用的,如果你試圖證明沒有解決方案。考慮到填充板的一種方法,您可以旋轉並反映該解決方案以找到填充板的其他方式。如果你有68K個起點,一個起點是另一個起點的旋轉或反射,你不需要同時嘗試兩個,因爲如果你能(或者可以)從一個起點解決問題,你可以從中得到答案另一個起點是通過旋轉或反射板子。因此,您可能能夠將您需要嘗試的起點數除以某個整數。

這個問題並不是每個階段都有大量替代品的唯一問題。這也影響旅行商問題。如果你能接受沒有保證你會找到絕對的最佳答案,你可以嘗試不跟蹤這些68k選擇中最不有希望的。您需要某種形式的分數來決定要保留哪些分數 - 您可能希望儘可能使用盡可能多的字母。一些針對旅行商問題的程序很早就放棄了節點之間的無用鏈接。丟棄部分解決方案而不是進行全深度優先搜索或分支定界的更一般方法是有限差異搜索 - 例如參見http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2426

當然,一些TSP丟棄樹搜索的方法完全贊成某種爬山方法。你可能會從一個充滿魔力的方格開始,然後反覆嘗試在其中找到你的單詞,修改幾個字符以強制它們,試圖找到連續增加可以在方塊中找到的單詞數量的步驟。最簡單的登山形式是從多個隨機起點重複簡單爬山。另一種方法是到目前爲止只通過隨機化一部分解決方案來重新開始爬山 - 因爲您不知道最佳大小的部分要隨機化,所以您可能會決定選擇部分的大小隨機隨機化,以便在至少有一小部分時間是隨機化正確大小的區域以產生新的方塊。遺傳算法和模擬退火在這裏非常流行。一篇關於一個新想法的論文,即Late Acceptance Hill-Climbing,也描述了其一些競爭對手 - http://www.cs.nott.ac.uk/~yxb/LAHC/LAHC-TR.pdf

+0

早期檢查很好。我儘快丟棄木板。但是,想象一個3個字母的單詞(例如:'abc')。這3個字母單詞在4x4網格上生成408個有效的佈局。當你做第二個字時,你會得到另一組佈局。現在您需要檢查這兩個覆蓋圖是否有效。 – Richard

+0

至於對稱性,對於一組408個以上的板子,其中一塊將是其他塊子的反射或旋轉。但是,當你爲單詞#2生成電路板時,仍然需要根據Word 1的佈局檢查所有(包括所有對稱性)電路板佈局......所以看起來你只能保存4個旋轉和4個鏡像。這可以將比較從68k x 68k減少到4k x 68k(並且每增加7個字母詞額外增加68k!) 這很多,但僅僅在30秒內完成這個運行是不夠的。 – Richard

+0

有大量的替代品也影響旅行推銷員問題的程序。在http://www.akira.ruc.dk/~keld/research/LKH/LKH-2.0/DOC/LKH_REPORT.pdf P 15 3.2(5)節中,您可以看到TSP的一種方法如何丟棄除最有希望的選擇我編輯了我的答案,以添加指示交易速度準確性的技巧。 – mcdowella

1

這是一個有趣的問題。我不能完全想出一個完整的優化解決方案,但這裏有一些想法可以嘗試。

困難的部分是要找到最佳的子集,如果你不能適應所有的單詞。這將增加很多的複雜性。從消除顯然不起作用的文字組合開始。用大於16個字母的單詞進行剪切。統計所需的唯一字母的數量。請務必考慮到同一個詞中重複的字母。例如,如果列表中包含「鷹」,我認爲您不允許在單詞的兩端使用相同的'e'。如果您需要的字母列表大於16,則必須刪除一些字詞。確定首先要切割的是一個有趣的子問題......我會從包含最少使用字母的單詞開始。這可能有助於讓所有子列表按分數排序。

然後你可以做一些微不足道的情況,其中字長的總和是< 16.之後,你從單詞的完整列表開始,看看是否有解決方案。如果沒有,找出哪些字要放下並再試一次。

給出一個單詞列表然後,核心算法是找到一個網格(如果存在的話),其中包含所有這些單詞的 。

愚蠢的蠻力方式是用所需的字母遍歷所有可能的網格,然後測試每一個網格,看看你的單詞是否合適。這非常苛刻:中間情況是16! = 2x10exp13板。 n個唯一字母的精確公式是...(16!)/(16-n)! x pow(n,16-n)。這在3x10exp16的範圍內給出了最壞的情況。不是很管理。 即使你可以避免旋轉和翻轉,這隻會爲你節省1/16的搜索空間。

一個稍微聰明的貪婪算法是按照某些標準對單詞進行排序,如難度或長度。遞歸解決方案是將剩餘的頂部單詞放在列表中,並嘗試將其放在網格上。然後遞歸該網格和剩餘的單詞列表。如果在用完單詞之前填滿網格,則必須回溯並嘗試另一種放置單詞的方式。比較貪婪的做法是嘗試首先重複使用大多數字母的展示位置。 你可以做一些修剪。如果在任何時候網格中剩餘的空間數少於所需的剩餘唯一字母集,則可以消除這些子樹。還有其他一些情況,很明顯沒有可以切割的解決方案,特別是當剩餘的網格空間是最後一個詞的長度的時候(尤其是<)。 搜索空間取決於字長和多少字母被重複使用。我確信它比蠻力更好,但我不知道是否足以使問題合理。

聰明的方法是使用某種形式的動態編程。我無法完全看到完整的算法。一個想法是有一個樹或圖形的字母,將每個字母連接到單詞列表中的「相鄰」字母。然後,從最連接的字母開始,嘗試將樹映射到網格上。始終放置完成單詞列表最多的字母。必須有一些方法來處理網格中同一個字母的多個情況。我不知道如何訂購它,所以你不必搜索每個組合。

最好的辦法是有一個動態算法,也包括所有的子詞列表。因此,如果列表中有「霧」和「狐狸」,並且狐狸不適合,但霧有,它將能夠處理,而不必在這兩個版本的列表上運行整個事情。這增加了複雜性,因爲現在您必須按照分數對每個解決方案進行排名。但在所有單詞不適合的情況下,這將節省大量時間。

祝你好運。

+0

那麼,我花了一個小時編碼遺傳算法,它可以找到單詞,但只有約50%(例如:12個單詞中的6個),即使是50k代。我使用標準的1分交叉和變異,並保持最佳狀態,直到新的種子板。 在附註中,我找到了這個:http://dinsights.com/POTM/BOGGLE/whoami.php但是即使是最好的輸入也是一個GA,跑了10分鐘。 此外:http://www.hees.us/reverseboggle.cgi在幾秒鐘內解決了這個問題。但是我無法弄清楚比爾真的在做什麼,甚至根據他給我的簡短描述。 – Richard

相關問題