有两个部分可以得到你想要的。
第一个是使用
call_semidet/1
它确保只有一个答案。也可以查看 SICStus 实现的链接。万一有多个答案,call_semidet/1
会产生一个安全错误。请注意,
once/1
仅此而已,!/0
就可以简单地删除所有存在的东西。
但是,你不会很高兴call_semidet/1
独自一人。它基本上执行了两次目标。一次看是否只有一个答案,然后才能再次获得第一个答案。所以你会在很久以后得到你的答案。
另一部分是加快你的定义,这样上面就不会对你造成太大的干扰。CapelliC 建议的解决方案完全改变了特定于您的具体功能但不扩展到任何其他功能的算法。但它也描述了一种不同的关系。
本质上,你已经找到了典型的部分,你只需要稍微不同地组装它们就可以使它们工作。但是,让我们从基础开始。
CLPFD 作为 CLP(Z)
对于许多 Prolog 程序员来说,你在这里所做的仍然不是那么常见。您将有限域约束用于一般整数算术。也就是说,您使用 CLPFD 作为在 等中找到的调制表达式求值的纯粹替代(is)/2
品(>)/2
。所以你想扩展有限域范式,它假设我们在有限的给定区间内表达一切。事实上,将这个扩展称为 CLP(Z) 会更合适。
此扩展不适用于每个提供有限域的 Prolog。事实上,只有 SICStus、SWI 和 YAP 能够正确处理无限区间的情况。其他系统在应该成功或失败时可能会失败或成功 - 主要是在整数变得太大时。
了解不终止
第一个问题是了解您的原始程序没有终止的实际原因。为此,我将使用失败切片。也就是说,我将false
目标添加到您的程序中。关键是:如果结果程序没有终止,那么原始程序也不会终止。因此,您的(假定的)原始程序的最小失败部分是:
fiborig(0,0) :-假的。
fiborig(1,1) :-假的。
fiborig(N,F) :-
N#> 1,
N1 #= N-1,
N2 #= N-2,
F #= F1+F2,
fiborig(N1,F1),假,
fiborig(N2,F2)。
这里有两个不终止的来源:一个是对于给定
的和F
有无限多个值。这可以通过观察很容易处理。F1
F2
F1 #> 0, F2 #>= 0
另一个与Prolog的执行机制有关。为了说明这一点,我将添加F2 #= 0
. 同样,因为生成的程序不会终止,所以原始程序也会循环。
fiborig(0,0) :-假的。
fiborig(1,1) :-假的。
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)。
所以实际的问题是,可能0
作为结果的目标执行得太晚了。只需交换两个递归目标。并添加冗余约束F2 #=< F1
以提高效率。
纤细(0,0)。
纤维蛋白(1,1)。
纤维蛋白(N,F):-
N#> 1,
N1 #= N-1,
N2 #= N-2,
F1 #> 0,
F2 #>= 0,
F1 #>= F2 ,
F #= F1+F2,
纤维蛋白(N2,F2),
纤维蛋白(N1,F1)。
在我蹩脚的笔记本电脑上,我得到了以下运行时fib(N, 377)
:
SICStus SWI
答案/总数
fiborig: 0.090s/27.220s 1.332s/1071.380s
fibmin: 0.080s/ 0.110s 1.201s/ 1.506s
取两者的总和以获得call_semidet/1
.
请注意,SWI 的实现仅用 Prolog 编写,而 SICStus 部分用 C 语言编写,部分用 Prolog 编写。因此,当将 SWI(实际上是 @mat 的)clpfd 移植到 SICStus 时,它的速度可能相当。
还有很多东西需要优化。想想索引和“计数器”的处理,N
, N1
, N2
。
您的原始程序也可以改进很多。例如,您不必要地发布了F #>= N-1
3 次约束!