在首次解决Fibonacci对之后防止回溯

时间:2014-01-19 11:24:24

标签: prolog fibonacci backtracking clpfd failure-slice

fib(N,F)F斐波纳契数时,N一词为真。

以下Prolog代码通常适用于我:

:-use_module(library(clpfd)).
fib(0,0).
fib(1,1).
fib(N,F) :- 
  N #> 1, 
  N #=< F + 1, 
  F #>= N - 1,
  F #> 0, 
  N1 #= N - 1, 
  N2 #= N - 2, 
  F1 #=< F, 
  F2 #=< F,
  F #= F1 + F2, 
  fib(N1,F1),
  fib(N2,F2).

执行此查询时(在SICStus Prolog中),找到N的第一个(和正确的)匹配(而不是立即):

| ?- fib(X,377).
X = 14 ?

当继续(通过输入“;”)查看是否还有其他匹配(根据定义是不可能的)时,需要花费大量时间(与第一次匹配相比),只是总是回答否:< / p>

| ?- fib(X,377).
X = 14 ? ;
no

作为Prolog的新手,我尝试以各种方式使用Cut-Operator(!),但是在第一场比赛后我找不到阻止搜索的方法。根据上述规则,它是否可能?如果是,请告诉我如何:)

3 个答案:

答案 0 :(得分:8)

有两个部分可以得到你想要的东西。

首先是使用 call_semidet/1 这确保只有一个答案。查看链接 SICStus的实现也是如此。万一有更多 一个答案,call_semidet/1会产生安全错误。注意 仅once/1!/0就可以简单地删除所有内容。

但是,仅凭call_semidet/1你就不会很满意。它 基本上执行两次目标。一旦看看是否没有更多 不止一个答案,只有这样才能获得第一个答案。所以 你会在以后得到答案。

另一部分是加快你的定义,以上不会 太让你不安CapelliC建议的解决方案发生了变化 完全由你的具体功能决定的算法 但不扩展到任何其他功能。但它也描述了一个 不同的关系。

基本上,你已经找到了典型的部分,你只需要 组装它们有点不同,使它们工作。但是,让我们开始吧 基础知识。

CLPFD为CLP(Z)

你在这里做的事情对许多Prolog来说仍然不常见 程序员。您将有限域约束用于一般整数 运算。也就是说,您正在使用CLPFD作为纯粹的替代品 (is)/2(>)/2false中的模式表达式评估 喜欢。所以你想扩展假设的有限域范式 我们在有限的给定间隔内表达一切。事实上,它 将此扩展称为CLP(Z)更合适。

这个扩展在每个Prolog提供有限的版本中都不起作用 域。事实上,只有正确的SICStus,SWI和YAP 处理无限间隔的情况。其他系统可能会失败或 当他们宁愿成功或失败时成功 - 主要是整数时 变得太大了。

了解非终止

第一个问题是了解原始的原因 程序没有终止。为此,我将使用failure slice。那 是的,我在您的计划中添加了 F 目标。关键是:如果 结果程序不会终止然后也是原始程序 不会终止。所以你的最小失败片(推测) 原始程序是:

fiborig(0,0) :- false.
fiborig(1,1) :- false.
fiborig(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F #= F1+F2,
   fiborig(N1,F1), false,
   fiborig(N2,F2).

这里有两个非终止来源:一个是给定的 F1 F2F1 #> 0, F2 #>= 0有无限多的值。那可以 通过观察F2 #= 0来轻松处理。

另一个与Prolog的执行机制更相关。至 说明一下,我将添加0。再次,因为结果 程序不会终止,原始程序也会循环。

fiborig(0,0) :- false.
fiborig(1,1) :- false.
fiborig(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F #= F1+F2,
   F1 #> 0,
   F2 #>= 0,
   F2 #= 0,
   fiborig(N1,F1), false,
   fiborig(N2,F2).

因此,实际问题是可能导致F2 #=< F1的目标 执行得太晚了。只需交换两个递归目标。并添加 效率的冗余约束fib(N, 377)

fibmin(0,0).
fibmin(1,1).
fibmin(N,F) :-
   N #> 1,
   N1 #= N-1,
   N2 #= N-2,
   F1 #> 0,
   F2 #>= 0,
   F1 #>= F2,
   F #= F1+F2,
   fibmin(N2,F2),
   fibmin(N1,F1).

在我的蹩脚笔记本电脑上,我获得了call_semidet/1的以下运行时间:

               SICStus                  SWI
             answer/total
fiborig:     0.090s/27.220s           1.332s/1071.380s
fibmin:      0.080s/ 0.110s           1.201s/   1.506s

取两者的总和来获得N的运行时。

请注意,SWI的实现仅使用Prolog编写,而 SICStus部分在C中,部分在Prolog中。因此,当将SWI(实际上是@mat的)clpfd移植到 SICStus,速度可能相当。

还有很多事情需要优化。想想索引,以及 处理“计数器”,N1N2F #>= N-1


此外,您的原始程序可以改进很多。例如, 您不必要地发布约束{{1}}三次!

答案 1 :(得分:5)

如果您只对第一个解决方案感兴趣或知道最多只有一个解决方案,您可以使用once/1提交该解决方案:

?- once(fib(X, 377)).

+1使用CLP(FD)作为低级算术的声明性替代。您的版本可以在所有方向上使用,而基于原始整数算术的版本则不能。

答案 2 :(得分:4)

我用另一个定义玩了一下,我用标准算法写了并且故意翻译成CLP(FD)这个问题。

我的普通Prolog定义是

fibo(1, 1,0).
fibo(2, 2,1).
fibo(N, F,A) :- N > 2, M is N -1, fibo(M, A,B), F is A+B.

一旦翻译,因为它在反向模式下花费的时间太长(或者没有终止,不知道), 我试图添加更多约束(并移动它们)以查看“向后”计算终止的位置:

fibo(1, 1,0).
fibo(2, 2,1).
fibo(N, F,A) :-
  N #> 2,
  M #= N -1,
  M #>= 0,  % added
  A #>= 0,  % added
  B #< A,   % added - this is key
  F #= A+B,
  fibo(M, A,B). % moved - this is key

添加B #< A并在最后一次调用时移动递归后,现在可以正常工作。

?- time(fibo(U,377,Y)).
% 77,005 inferences, 0.032 CPU in 0.033 seconds (99% CPU, 2371149 Lips)
U = 13,
Y = 233 ;
% 37,389 inferences, 0.023 CPU in 0.023 seconds (100% CPU, 1651757 Lips)
false.

编辑要考虑基于0的序列,请添加一个事实

fibo(0,0,_).

也许这解释了最后一个论点的作用:它是一个累加器。