Prolog:怎么做“检查(a ++ b ++ c ++ d等于d ++ a ++ c ++ b) - >是的”

时间:2011-08-29 14:21:15

标签: prolog combinatorics gnu-prolog

让我们定义自定义运算符 - 让它为++equals

:- op(900, yfx, equals).
:- op(800, xfy, ++).

事实:

check(A equals A).

我尝试创建谓词,让它为check/1,在以下所有情况下都会返回true:

check( a ++ b ++ c ++ d equals c ++ d ++ b ++ a ),
check( a ++ b ++ c ++ d equals d ++ a ++ c ++ b),
check( a ++ b ++ c ++ d equals d ++ b ++ c ++ a ),
% and all permutations... of any amount of atoms
check( a ++ ( b ++ c ) equals (c ++ a) ++ b),
% be resistant to any type of parentheses

返回

yes

如何在Prolog中实现此功能?(请摘下代码段。可能吗?我错过了什么吗?)

Gnu-Prolog是首选,但SWI-Prolog也是可接受的。

P.S。请将代码视为草稿“伪代码”,并且不关心小的语法问题。

P.P.S'++'才刚刚开始。我想添加更多运营商。这就是为什么我担心把东西列入清单可能不是一个好办法。

另外

此外,如果可以进行查询会很好(但是,这部分不是必需的,如果你能够回答第一部分,那就太棒了)

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

可能的结果之一(感谢@mat显示其他人)

X=c, Y=a

我主要寻找第一部分问题的解决方案 - “是/否”检查。

X,Y的第二部分将是很好的补充。在它X中,Y应该是简单的原子。对于X的上述示例域,指定了Y:domain(X,[a,b,c]),domain(Y,[a,b,c])

2 个答案:

答案 0 :(得分:6)

您的表示称为“defaulty”:为了处理此表单的表达式,您需要一个“默认”大小写,或者显式检查atom / 1(这不是单调) - 您不能直接使用模式匹配来处理所有情况。因此,请再次考虑您的情况:

check( a ++ (b ++ X) equals (c ++ Y) ++ b) )

你说这应该回答X=c, Y=a。但是,它也可以回答X = (c ++ d), Y = (a ++ d)。该解决方案是否也应该出现如果不是,它就不会是单调的,因此会使程序的声明性调试和推理变得非常复杂。在您的情况下,将这样的表达式表示为列表是否有意义?例如,[a,b,c,d]等于[c,d,b,a]?然后,您可以简单地使用库谓词置换/ 2来检查此类“表达式”的相等性。

当然也可以使用默认表示,并且在许多情况下它们可能对用户更方便(考虑Prolog源代码本身及其默认的目标表示法或Prolog算术表达式)。您可以使用非单调谓词(如var / 1和atom / 1),以及术语检查谓词(如functor / 3和(= ..)/ 2)来系统地处理所有情况,但它们通常会阻止或至少阻止良好的声明性解决方案可以在所有方向上使用,以测试并生成所有案例。

答案 1 :(得分:0)

这个问题相当陈旧,但无论如何我都会发布我的解决方案。我在业余时间学习prolog,发现这是一个非常具有挑战性的问题。

我学到了很多关于DCG和差异列表的知识。我担心,我没有想出一个不使用列表的解决方案。像mat建议的那样,它将术语转换为平面列表以处理括号,并使用permutation/2来匹配列表,从而解释了++运算符的交换性质......

以下是我提出的建议:

:- op(900, yfx, equ).
:- op(800, xfy, ++).

check(A equ B) :- A equ B.

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

sum_pp(Term, List, Len) :- sum_pp_(Term, List,[], 0,Len).

sum_pp_(A, [A|X],X, N0,N) :- nonvar(A), A\=(_++_), N is N0+1.
sum_pp_(A, [A|X],X, N0,N) :- var(A), N is N0+1.
sum_pp_(A1++A2, L1,L3, N0,N) :-
    sum_pp_(A1, L1,L2, N0,N1), (nonvar(N), N1>=N -> !,fail; true),
    sum_pp_(A2, L2,L3, N1,N).

sum_pp/3谓词是主力,它使用一个术语并将其转换为一个平坦的加数列表,返回(或检查)长度,同时不受括号的影响。它与DCG规则(使用差异列表)非常相似,但它被编写为常规谓词,因为它使用长度来帮助打破左递归,否则会导致无限递归(花了我很长时间才能打败它)

它可以查看地面条款:

?- sum_pp(((a++b)++x++y)++c++d, L, N).
L = [a,b,x,y,c,d],
N = 6 ;
false.

它还会生成解决方案:

?- sum_pp((b++X++y)++c, L, 5).
X = (_G908++_G909),
L = [b,_G908,_G909,y,c] ;
false.

?- sum_pp((a++X++b)++Y, L, 5).
Y = (_G935++_G936),
L = [a,X,b,_G935,_G936] ;
X = (_G920++_G921),
L = [a,_G920,_G921,b,Y] ;
false.

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G827++_G828),
L = [_G827,_G828],
N = 2 ;
Y = (_G827++_G836++_G837),
L = [_G827,_G836,_G837],
N = 3 .

equ/2运算符“统一”两个术语,如果存在变量,还可以提供解决方案:

?- a++b++c++d equ c++d++b++a.
true ;
false.

?- a++(b++c) equ (c++a)++b.
true ;
false.

?- a++(b++X) equ (c++Y)++b.
X = c,
Y = a ;
false.

?- (a++b)++X equ c++Y.
X = c,
Y = (a++b) ;
X = c,
Y = (b++a) ;
false.

在equ / 2规则

equ(A,B) :- sum_pp(A,AL,Len), sum_pp(B,BL,Len), !, permutation(AL, BL).

对sum_pp的第一次调用生成一个长度,而第二次调用将长度作为约束。剪切是必要的,因为第一次调用可能会继续生成不断增长的列表,这些列表将永远不会再与第二个列表匹配,从而导致无限递归。我还没有找到更好的解决方案......

感谢发布这样一个有趣的问题!

/彼得

编辑:sum_pp_写为DCG规则:

sum_pp(Term, List, Len) :- sum_pp_(Term, 0,Len, List, []).
sum_pp_(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_(A1++A2, N0,N) -->
    sum_pp_(A1, N0,N1), { nonvar(N), N1>=N -> !,fail; true },
    sum_pp_(A2, N1,N).

更新

sum_pp(Term, List, Len) :-
    (    ground(Term)
    ->   sum_pp_chk(Term, 0,Len, List, []), !  % deterministic
    ;    length(List, Len), Len>0,
         sum_pp_gen(Term, 0,Len, List, [])
    ).

sum_pp_chk(A, N0,N) --> { A\=(_++_), N is N0+1 }, [A].
sum_pp_chk(A1++A2, N0,N) --> sum_pp_chk(A1, N0,N1), sum_pp_chk(A2, N1,N).

sum_pp_gen(A, N0,N) --> { nonvar(A), A\=(_++_), N is N0+1 }, [A].
sum_pp_gen(A, N0,N) --> { var(A), N is N0+1 }, [A].
sum_pp_gen(A1++A2, N0,N) -->
    { nonvar(N), N0+2>N -> !,fail; true }, sum_pp_gen(A1, N0,N1),
    { nonvar(N), N1+1>N -> !,fail; true }, sum_pp_gen(A2, N1,N).

我把sum_pp分成两个变种。第一个是修剪版本,检查地面术语并且是确定性的。第二个变体调用length/2来执行某种迭代加深,以防止左递归在右递归有机会产生某些东西之前逃跑。与每个递归调用之前的长度检查一起,现在可以获得比以前更多的无限递归证明。

特别是现在可以使用以下查询:

?- sum_pp(Y, L, N).
L = [Y],
N = 1 ;
Y = (_G1515++_G1518),
L = [_G1515,_G1518],
N = 2 .

?- sum_pp(Y, [a,b,c], N).
Y = (a++b++c),
N = 3 ;
Y = ((a++b)++c),
N = 3 ;
false.