Prolog: how to do “check(a++b++c++d equals d++a++c

2019-04-09 11:43发布

问题:

Let's define custom operators - let it be ++,equals

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

And fact:

check(A equals A).

I try to make predicate, let it be check/1, that will return true in all following situations:

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

return

yes

How to implement this in Prolog? (Code snippet, please. Is it possible? Am I missing something?)

Gnu-Prolog is preferred, but SWI-Prolog is acceptable as well.

P.S. Please treat code, as draft "pseudocode", and don't care for small syntax issues.

P.P.S '++' is just beginning. I'd like to add more operators. That's why I'm afraid that putting stuff into list might be not good solution.

Additionally

Additionally, would be nice, if queries would be possible (but, this part is not required, if you are able to answer to first part, it's great and enough)

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

one of possible results (thanks @mat for showing others)

X=c, Y=a

I am looking mostly for solution for first part of question - "yes/no" checking.

Second part with X,Y would be nice addition. In it X,Y should be simple atoms. For above example domains for X,Y are specified: domain(X,[a,b,c]),domain(Y,[a,b,c]).

回答1:

Your representation is called "defaulty": In order to handle expressions of this form, you need a "default" case, or explicitly check for atom/1 (which is not monotonic) - you cannot use pattern matching directly to handle all cases. As a consequence, consider again your case:

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

You say this should answer X=c, Y=a. However, it could also answer X = (c ++ d), Y = (a ++ d). Should this solution also occur? If not, it would not be monotonic and thus significantly complicate declarative debugging and reasoning about your program. In your case, would it make sense to represent such expressions as lists? For example, [a,b,c,d] equals [c,d,b,a]? You could then simply use the library predicate permutation/2 to check for equality of such "expressions".

It is of course also possible to work with defaulty representations, and for many cases they might be more convenient for users (think of Prolog source code itself with its defaulty notation for goals, or Prolog arithmetic expressions). You can use non-monotonic predicates like var/1 and atom/1, and also term inspection predicates like functor/3 and (=..)/2 to systematically handle all cases, but they usually prevent or at least impede nice declarative solutions that can be used in all directions to test and also generate all cases.



回答2:

This question is rather old, but I'll post my solution anyways. I'm learning prolog in my spare time, and found this quite a challenging problem.

I learned a lot about DCG and difference lists. I'm afraid, I didn't come up with a solution that does not use lists. Like mat suggested, it transforms terms into flat lists to cope with the parentheses, and uses permutation/2 to match the lists, accounting for the commutative nature of the ++ operator...

Here's what I came up with:

:- 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).

The sum_pp/3 predicate is the workhorse which takes a term and transforms it into a flat list of summands, returning (or checking) the length, while being immune to parentheses. It is very similar to a DCG rule (using difference lists), but it is written as a regular predicate because it uses the length to help break the left recursion which would otherwise lead to infinite recursion (took me quite a while to beat it).

It can check ground terms:

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

It also generates solutions:

?- 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 .

The equ/2 operator "unifies" two terms, and can also provide solutions if there are variables:

?- 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.

In the equ/2 rule

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

the first call to sum_pp generates a length, while the second call takes the length as a constraint. The cut is necessary, because the first call may continue to generate ever growing lists that will never again match with the second list, leading to infinite recursion. I haven't found a better solution yet...

Thanks for posting such an interesting problem!

/Peter

edit: sum_pp_ written as DCG rules:

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).

update:

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).

I split sum_pp into two variants. The first is a slim version that checks ground terms and is deterministic. The second variant calls length/2 to perform some kind of iterative deepening, to prevent the left-recursion from running away before the right recurson gets a chance to produce something. Together with the length checks before each recursive call, this is now infinite recursion proof for many more cases than before.

In particular the following queries now work:

?- 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.