listconstraint-programminglogic-programmingpicat

Integer constraints and difference of two lists


I am trying to find a value score, which can be calculated imperatively from two equal length lists Xs, Ys as:

for i in len(Xs):
    if Xs[i] == Ys[i]:
        score++
    else:
        score--

So the idea is to basically check every element from left to right on both lists and increment score if those elements are the same, decrement score otherwise. For example:

score([1,2,3], [1,2,3]) == 3
score([1,2,3], [1,2,1]) == 1
score([1,2,3], [3,2,1]) == -1
score([1,2,3], [3,1,2]) == -3

I think I actually managed to write the program as:

score(Xs, Ys, C) ?=>
    Xs = [], Ys = [], C = 0.

score(Xs, Ys, C) =>
    Xs = [XH|XT],
    Ys = [YH|YT],
    if (XH #= YH) then
        C #= C1 + 1
    else
        C #= C1 - 1
    end,
    score(XT, YT, C1).

the code gives the correct results when I query the score:

score([1,2,3], [1,2,3], S).
    S = 3 ?;

score([1,2,3], [1,2,1], S).
    S = 1 ?;

score([1,2,3], [3,2,1], S).                                      
    S = -1 ?;

score([1,2,3], [3,1,2], S).                                      
    S = -3 ?;

However, when I try to generate the lists which produce the scores, the code only generates the identical list with score 3:

S :: -3..3, Ls = new_list(3), Ls :: 1..3, score([1,2,3], Ls, S), solve([Ls, S]).

S = 3
Ls = [1,2,3] ?;

no

I want to generate all the lists, corresponding to all the possible scores, I have a feeling that I have to modify the base case but I can't figure out how :)

EDIT: I also tried to do a tail-recursion type of solution, it again gives correct results when the scores are queried, but it can only generate the identical list.

score(Xs, Ys, C) ?=>
    score_helper(Xs, Ys, 0, C).

score_helper([], _, Cur, C) ?=>
    C = Cur.

score_helper(Xs, Ys, Cur, C) ?=>
    Xs = [XH|XT],
    Ys = [YH|YT],
    if (XH = YH) then
        score_helper(XT, YT, Cur+1, C)
    else
        score_helper(XT, YT, Cur-1, C)
    end.

Solution

  • If you are using constraint solver (such as cp, sat, etc) then simply using sum should do the job:

    import cp. 
    
    go ?=> 
      X=new_list(3),
      Y=new_list(3),
      X::1..3, 
      Y::1..3, 
      Z #= sum([cond(X[I] #= Y[I], 1,-1) : I in 1..3]),
      solve(X++Y),
      println([x=X,y=Y,z=Z]), 
      fail,
      nl.
    go => true.
    

    The main point is that one can use constraints (here X[I] #= Y[I]) inside the list comprehension.

    Or is it a requirement that it should no use constraints?

    Edit: Another CP approach is the following (using "Prolog" style which is supported by Picat v3. This use equivalence (#<=>) for the check:

    go3 ?=>
      Y = [1,2,3],
      X = new_list(3),
      X :: 1..3,
      Score :: [-1,1],
      score3(X,Y,0,Score),
      solve(X ++ [Score]),
      println(x=X),
      println(score=Score),
      nl,
      fail,
      nl.
    go3 => true.
    
    
    score3([], [], Score,Score).
    score3([XH|XT], [YH|YT], Score0, Score) :-
      (XH #= YH) #<=> (Score1 #= Score0 + 1),
      score3(XT, YT, Score1,Score).
    

    One problem with your approach is the you mix constraint equality check (#=) with traditional equality (== and =) which might not give the expected results.

    And here's yet another approach using the same principle but inside a foreach loop:

     go2 ?=>
       Y = [1,2,3],
       N = Y.len,
       X = new_list(N),
       X :: 1..max(Y),
       Scores = new_list(N),
       Scores :: [-1,1],
       foreach(I in 1..X.len)
          X[I] #= Y[I] #<=> Scores[I] #= 1
       end,
       Z #= sum(Scores),
       solve(X ++ Scores),
       println([x=X,y=Y,scores=Scores,z=Z]),
      fail,
      nl.
    go2 => true.