Search code examples
prologclpfd

Stop Prolog from inferring values


If I run solved([[x, o, o], [o, o, o], [o, o, o]]) it should output true as there is only x and if I run solved([[x, o, o], [o, o, o], [o, o, x]]) it should output false as there is more than 1 x. However, running it outputs true all the time as it infers a value for C.

:- use_module(library(clpfd)).

rotate_clock(Xss, Zss) :-
   transpose(Xss, Yss),
   maplist(reverse, Yss, Zss).

rotate_anti(Xss, Zss) :-
   maplist(reverse, Xss, Yss),
   transpose(Yss, Zss).

linjmp([x, x, o | T], [o, o, x | T]).
linjmp([o, x, x | T], [x, o, o | T]).
linjmp([H|T1], [H|T2]) :- linjmp(T1,T2).

horizjmp([A|T],[B|T]) :- linjmp(A,B).
horizjmp([H|T1],[H|T2]) :- horizjmp(T1,T2).

jump(B,A) :- horizjmp(B,A).
jump(B,A) :- rotate_clock(B,BR), horizjmp(BR,BRJ), rotate_anti(BRJ, A).

num_x(A, C) :- count(A, x, C).

count([],X,0).
count([X|T],X,Y):- count(T,X,Z), Y is 1+Z.
count([_|T],X,Z):- count(T,X,Z).

sum_list([], 0).
sum_list([H|T], Sum) :-
   sum_list(T, Rest),
   Sum is H + Rest.

solved(A) :-
   maplist(num_x, A, B),
   sum_list(B, C),
   C == 1.

Solution

  • Instead of trying to change the behavior of a language (which might be possible, but is of course a challenge), it might be better to investigate why eventually Prolog finds a C with C == 1. If we evaluate the maplist(num_x, A, B), ourselves, we see:

    ?- maplist(num_x, [[x, o, o], [o, o, o], [o, o, x]], B).
    B = [1, 0, 1] ;
    B = [1, 0, 0] ;
    B = [0, 0, 1] ;
    B = [0, 0, 0].
    

    So it appears that the num_x/2 predicate, can generate multiple results, for the same list: for a list with one x, it first generates 1, and then 0.

    This is confirmed if we do some tests with count/3:

    ?- count([x, o, o], x, C).
    C = 1 ;
    C = 0.
    
    ?- count([x, x, o], x, C).
    C = 2 ;
    C = 1 ;
    C = 1 ;
    C = 0.
    

    So it appears that count/3 each time has a backtracking point where it can decide to count a given x, or not.

    Indeed, if we take a look at the count/3 predicate, we see:

    count([],X,0).
    count([X|T],X,Y):- count(T,X,Z), Y is 1+Z.
    count([_|T],X,Z):- count(T,X,Z).

    So here for a non-empty list, there are two ways clauses: one where the head is equal to X, the element we want to count, in which case Y is 1+Z, but the last clause says that, regardless what the value of the head is, Prolog will not count that element. Prolog performs backtracking, and thus will eventually pick both clauses.

    We can add a dif/2 to add a constraint that the head and X should be differrent, like:

    count([],X,0).
    count([X|T],X,Y):- count(T,X,Z), Y is 1+Z.
    count([H|T],X,Z):- dif(H, X), count(T,X,Z).

    So now if an X appears in the list, we will count that element.