在填字游戏式拼图中回溯列表

时间:2014-10-08 04:59:45

标签: prolog logic crossword

我正在创建一个程序,它将获取一个单词列表和一个正方形,填字游戏样式的空格网格,并返回唯一的解决方案,即填充的填字游戏,使所有单词一致地组合在一起。网格的大小是任意的,但它总是一个正方形。

请点击此处查看我正在尝试做的示例: http://en.wikipedia.org/wiki/Fill-In_(puzzle)

我对该计划有所了解。本质上,我的第一组谓词采用网格并为每个插槽创建逻辑变量,忽略了黑屏的插槽(#s)。然后,我创建一个大于1个槽长的可能单词列表(单个字母长的单词无效)。结果是一个有形状的网格(一个非常简单的例子):

#_#
___
#_#

每行是从上到下的“拼图列表”的元素,即

  Row 1   Row 2   Row 3
[[#,_,#],[_,_,_],[#,_,#]]

将填充免费的逻辑变量,如:

#X#
ABC
#Z#

列表看起来像(第0部分):

[[#,X,#],[A,B,C],[#,Z,#]]

然后,形式的词表:

[['M','E','N'],['N','E','W']]

给出,最终解决方案是

#M#
NEW
#N#

到目前为止,我使用第0部分中的变量填充网格列表,并且还填充了一个列表,其中包含可能插入的单词的插槽(“插槽列表”),其中为每个垂直和水平字符串创建一个插槽长度超过1个空格的空间(对于此示例):

[[A,B,C],[X,B,Z]]

所以我成功地进行了这些设置,这样将单词统一到插槽列表的插槽中也会将该单词统一到拼图列表中的匹配变量。

现在,所有的输入都是这样的,总是只有一种方法来排列网格中的单词(不像这个有两种方式的例子,只是忽略它),所以完成后只提供一种解决方案程序(解决方案是填充的拼图网格)。

单词统一算法应为:

1. Take a word from the word list
2. Go through the slot list and unify with the first slot that succeeds
3. If there are no successful bindings, fail and backtrack to try a new 
   set of unifications
4. Keep failing and backtracking until a solution is found such that all 
   words bind successfully

我将这些单词统一到插槽的代码如下:

%%% Takes a word (W) and a list of slots (S|Ss) and attempts to bind it to a slot
bind_word(W,[S|Ss],Slots):- bind_w(W,[S|Ss],[],Slots).
bind_w(_,[],Acc,Acc).
bind_w(W,[S|Ss],Acc,Slots):-
(   W = S ->                    % Does the word bind to the next slot?
    append(Acc,[S],Acc1),       % YES Add the bound slot to the accumulator 
    append(Acc1,Ss,Acc2),       % Add the rest of the slots to the accumulator
    bind_w(_,[],Acc2,Slots)     % Exit with success
;   length(Ss,0) -> fail        % NO Word doesn't bind, if there are no slots left to bind then this has failed
;   append(Acc,[S],Acc1),       % NO Word doesn't bind, but there are slots left, append this unbound slot to the accumulator
    bind_w(W,Ss,Acc1,Slots)     % Move to the next slot
).
%%%

我想要它做的是它是否命中

length(Ss,0) -> fail

然后回溯到开头并再次尝试,但不是再次尝试使用相同的绑定,而是将成功的绑定视为失败并跳过它们,例如:

1. Try to bind the word B,E,N to the first slot, and it works
2. Try to bind the word M,A,X to the second slot, and it doesn't 
   work and there are no slots left
3. Backtrack to (1), and instead of binding BEN to slot one (which 
   succeeded before), skip to the next slot and try unifying BEN to 
   the second slot.

不幸的是,当它命中时

length(Ss,0) -> fail

它认为整个事情都是失败的,并没有回溯,但却失败了。求解谓词如下:

%%% Takes a puzzle grid and a wordlist and solves the puzzle
solve_puzzle(Solution, [], Solution).
solve_puzzle(Puzzle, Wordlist, Solved):-
    fill_slots_H(Puzzle,Slots1,Puzzle1),        % Fill out the puzzle with logical variables in every blank space (results = Puzzle1), also get all the horizontal word slots (results = Slots1) 
    flip(Puzzle1,0,Puzzle1_Flipped),            % Flip the puzzle for vertical use (results = Puzzle1_Flipped)
    fill_slots_V(Puzzle1_Flipped,Slots1,Slots), % Take the vertical puzzle and append the slots for vertical words onto the end of the existing slot list SLOTS IS THE FINAL UNBOUND SLOT LIST
    flip(Puzzle1_Flipped,1,Puzzle_Final),       % Flip the puzzle back to normal PUZZLE_FINAL IS THE FINAL UNBOUND PUZZLE
    !,                                          % Make these choices final
    insert_words(Wordlist,Slots,Final_Slotlist),% Insert all the words into the slots and return the finished slot list 
    Slots = Final_Slotlist,                     % Bind all logical variables in the final slotlist to the slotlist linked to the puzzle
    solve_puzzle(Puzzle_Final, [], Solved).     % Puzzle is now filled, return it as the solution
%%%

%%% Takes a (W)ordlist, and (S)lot list and binds every word to a slot until the puzzle is finished
insert_words([],Slots,Slots).
insert_words([W|Ws], Current_Slotlist, Filled_Slotlist):- 
    bind_word(W,Current_Slotlist,Partial_Slotlist),
    insert_words(Ws, Partial_Slotlist, Filled_Slotlist).
%%%

我填写拼图,获取水平字槽的列表,然后转置拼图并获得垂直字槽的列表(将它们附加到水平字槽)。然后,我用单词填写插槽列表,用空槽列表统一填充列表(这也将单词统一到拼图网格),然后返回完成的拼图。

如果它不能统一一个单词,它会回溯并跳过任何成功并尝试另一个选择,我该怎么做呢?我曾想过尝试绑定,然后如果它失败了,将单词列表随机化并重试,但这对我来说听起来并非逻辑驱动...

提前致谢。

2 个答案:

答案 0 :(得分:3)

您一直在考虑搜索算法,而对程序的逻辑含义却太少了。在Prolog中你必须做到这两点,并且可能需要一段时间才能找到合适的平衡点。

如果我理解正确的话,你想要的只是一个接一个地说,试着把它装进一个插槽,并按时间顺序回溯。这在Prolog很容易。要对单词列表中的所有单词执行某些操作,请使用递归。要尝试插槽列表中的插槽,请使用member / 2。回溯自动发生:

solve(Ss) :-
    Ws=[[b,e,g],[w,e,b],[n,e,w],[b,e,n]],
    Ss=[[A,B,C],[D,B,F],[F,H,I],[C,H,L]],
    all_member(Ws, Ss).

all_member([], _).
all_member([W|Ws], Ss) :-
    member(W, Ss),
    all_member(Ws, Ss).

?- solve(Ss).
Ss = [[b, e, n], [w, e, b], [b, e, g], [n, e, w]]
Yes (0.00s cpu, solution 1, maybe more)
Ss = [[w, e, b], [b, e, n], [n, e, w], [b, e, g]]
Yes (0.00s cpu, solution 2, maybe more)
No (0.01s cpu)

[我假设单词列表中的所有单词都不同]

PS:一旦你通过用disjunction替换if-then-else来纠正bind_w / 4,它实际上就变成了member / 2的复杂版本。你不需要累加器对和附加,因为他们所做的只是构建一个槽列表的副本(你不需要,只需使用原来的)。您也不需要bind_w / 4的第一个子句,因为您希望它以空槽列表失败。删除后,您不再需要长度测试。

答案 1 :(得分:0)

我实际上已经明白了。我不知道if-A-then-B-else-C会抛出Prolog中的任何选择点,因此A的所有排列都没有被探索,因为if-then-else会抛出所有其他排列。从bind_w中取出if-then-else并将其替换为:

must be slots remaining,
unify word ; unify next slot.

工作,因为存在失败条件(插槽剩余> 0)和选择点(与当前插槽统一字,或与下一个插槽统一)。如果命中失败条件,则Prolog将回溯并尝试不同的分支。

相关问题