问题描述
让我们使用这个函数:
function gcd(a,b)
while a ≠ b
if a > b
a := a − b
else
b := b − a
return a
我们如何在纯 Prolog 中编码 gcd/3,以便它可以反转。例如,Prolog 谓词应该计算 gcd(2,3)=1。但是如果我们问什么是 a,b 使得 gcd(a,b)=1,我们也会得到相同的 Prolog 谓词:
/* one while iteration */
2,1
1,2
/* two while iterations */
3,1
2,3
3,2
1,3
/* Etc... */
Prolog 似乎特别适合,因为它可以枚举解决方案。
解决方法
此解决方案使用参数来跟踪循环的当前层:
gcd(A,B,G):-
gcd(_,A,G).
gcd(Tier,G):-
Tier1 #= Tier - 1,Tier1 #>= 0,zcompare(Order,B),gcd(Order,Tier1,G).
gcd(=,G,G).
gcd(>,Tier,G):-
A1 #= A - B,gcd(Tier,A1,G).
gcd(<,G):-
B1 #= B - A,B1,G).
因此,当您想获得分层枚举时,我会写:
?- between(1,3,Tier),1),write(B/A),nl,fail; true.
1/1
1/2
2/1
1/3
2/3
3/2
3/1
true.
,
我首先尝试从字面上翻译 GCD 函数 成 Prolog 代码。第一个子句是当 a ≠ b 为假时, 这意味着我们可以终止该函数。否则我们 递归到两种情况:
euclid(A,A).
euclid(A,R) :- A #< B,C #= B-A,euclid(A,C,R).
euclid(A,R) :- A #> B,C #= A-B,euclid(C,R).
我们可以测试,似乎工作正常,除了它有选择点。 但选择点是我们必须付出的代价 使用 CLP(FD) 和编程纯 Prolog,无需剪切:
?- euclid(17,13,X).
X = 1 ;
No
但是使用 euclid/3 进行枚举不是很令人满意,
结果只是GCD函数的一个执行分支:
?- euclid(A,1).
A = 1,B = 1 ;
A = 1,B = 2 ;
A = 1,B = 3 ;
A = 1,B = 4 ;
现在我们可以执行以下操作并通过 GCD 对路径 P 进行编码 一个二进制数的函数。当我们终止路径时,路径将是 P=1。 否则我们使用 P 的低位来编码剩下的两个中的哪一个 选择了 GCD 的条款:
euclid(A,1,P,P #= 2*Q,Q #> 0,Q,P #= 2*Q+1,R).
生成的 Prolog 谓词确实是双向的:
?- euclid(17,X).
P = 241,X = 1 ;
No
?- euclid(A,241,1).
A = 17,B = 13 ;
No
我们也可以用它来任意枚举,虽然只能用
between/3 的帮助也许不是最有效的,但它确实有效:
?- between(1,7,P),fail; true.
1/1
2/1
1/2
3/1
2/3
3/2
1/3
Yes
编辑 04.02.2021:
哦,有趣的是,这也有效。但结果的顺序不同:
?- P #< 8,fail; true.
1/1
2/1
3/1
3/2
1/2
2/3
1/3
true.