【发布时间】:2016-05-28 13:17:18
【问题描述】:
我的教授给了this作为Prolog的一个例子。这是一个解决河内塔谜题的程序,您必须通过一个接一个地移动一个圆盘来将一堆圆盘移动到另一个钉子上,而不是将更大的圆盘放在较小的圆盘上。
现在,我不喜欢那个程序。有人告诉我 Prolog 是为声明式编程设计的。我不想编写如何解决问题的程序,我想用 Prolog 写下问题是什么。那就让Prolog来解决吧。
到目前为止,我的努力可以在下面找到。我使用了两种类型的列表,动作序列表示如下:[[1,2],[3,1]];这将是“将顶部磁盘从 peg 1 移动到 peg 2,将磁盘从 peg 3 移动到 peg 1”。我的第二种列表是状态,例如,如果有三个钉子[[1,2,3], [], []] 将意味着第一个钉子上有三个磁盘。较小的磁盘具有较小的数字,因此内部列表的前面是堆栈的顶部。
% A sequence of actions (first argument) is a solution if it leads
% from the begin state (second argument) to the End state (third argument).
solution([], X, X).
solution([[FromIdx | ToIdx] | T], Begin, End) :-
moved(FromIdx, ToIdx, Begin, X),
solution(T, X, End).
% moved is true when Result is the resulting state after moving
% a disk from FromIdx to ToIdx starting at state Start
moved(FromIdx, ToIdx, Start, Result) :-
allowedMove(FromIdx, ToIdx, Start),
nth1(FromIdx, Start, [Disk|OtherDisks]),
nth1(ToIdx, Start, ToStack),
nth1(FromIdx, Result, OtherDisks),
nth1(ToIdx, Result, [Disk|ToStack]).
allowedMove(FromIdx, ToIdx, State) :-
number(FromIdx), number(ToIdx),
nth1(FromIdx, State, [FromDisk|_]),
nth1(ToIdx, State, [ToDisk|_]),
ToDisk > FromDisk.
allowedMove(_, ToIdx, State) :- nth1(ToIdx, State, []).
上面的程序似乎可以工作,但是对于所有相当复杂的东西来说它太慢了。要求它解决经典的河内塔问题,将三个磁盘从第一个钉子移动到第三个也是最后一个钉子,会这样:
?- solution(Seq, [[1,2,3], [], []], [[], [], [1,2,3]]).
我想对程序进行一些修改,使其适用于该查询。我该怎么做呢?在分析时,我可以看到 nth1 使用了很多时间,我应该摆脱它吗?困扰我的是moved 是完全确定的,应该只有一个结果。如何加快这个瓶颈?
【问题讨论】:
-
我认为您的整个实施本质上是必不可少的。您是否在“河内序言塔”上进行了 stackoverflow.com 或谷歌搜索?有很多例子可以说明如何以更相关的方式做到这一点。
-
你能解释一下它的必要性吗?对于你的问题:是的,我有,但我能找到的只是我所链接的解决方案的风格,这是公然必要的。
-
我想我正在键入所有
nth1/3电话,正如您在问题中所指出的那样。有一些小问题需要修复,例如[FromIdx-ToIdx | T]之类的形式可能比[[FromIdx | ToIdx] | T]更合适。总的来说,您可以采用“标准”方法并使用 CLP(FD) (N #> 1, N #= N-1) 并为移动携带一个列表参数,而不是您在其他实现中通常看到的writes。 -
我做了一个快速而肮脏的转换,它几乎可以工作,除了像
hanoi(N, [...])这样的查询,它会为一组有效的河内移动产生N,但在找到解决方案之后它确实做到了有一些……呃……终止问题。 :p
标签: prolog dcg clpfd declarative towers-of-hanoi