4

The SICStus manual for the CLP(FD) library says:

nvalue(?N, +Variables) where Variables is a list of domain variables with finite bounds or integers, and N is an integer or a domain variable. True if N is the number of distinct values taken by Variables.

This is particularly useful when one wants to minimize the number of distinct values in the solution. For example, if one is trying to distribute stuff into bags of different sizes, and want to minimize the number of bags.

Is there an equivalent predicate (or way) for achieving the same in SWI Prolog?

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
Hugo Sereno Ferreira
  • 8,600
  • 7
  • 46
  • 92
  • Is that what [`fd_size/2`](http://www.swi-prolog.org/pldoc/man?predicate=fd_size/2) does? – Daniel Lyons Jan 05 '17 at 23:38
  • I don't think so... `fd_size/2` returns the number of elements of a domain. For example, `X in 0..10, fd_size(X, 11)`. I'm looking for something that constrains the number of *distinct* elements in a list of variables. – Hugo Sereno Ferreira Jan 05 '17 at 23:40
  • And you would expect `X in 0..10, foo([X], 1)`? – Daniel Lyons Jan 05 '17 at 23:42
  • I would expect `length(Vs, 3), Vs ins 1..3, nvalue(2, Vs)` to exclude as possible answers things like `Vs = [3, 3, 3]` or `Vs = [1, 2, 3]`, because we are saying that there must be two distinct values. – Hugo Sereno Ferreira Jan 05 '17 at 23:44
  • 2
    [Backlink](https://github.com/triska/clpz/issues/8) to improvements in clpz. – false Jan 29 '17 at 18:15

1 Answers1

4

After @jschimpf comment, I've rethought the algorithm.

nvalue(1, [_]).
nvalue(C, [V|Vs]) :-
    count_equals(V, Vs, E),
    E #= 0 #/\ C #= R+1 #\/ E #> 0 #/\ C #= R,
    nvalue(R, Vs).

count_equals(_, [], 0).
count_equals(V, [U|Vs], E) :-
    V #= U #/\ E #= E1+1 #\/ V #\= U #/\ E #= E1,
    count_equals(V, Vs, E1).

further cleanup

again, after @jschimpf note, I've tweaked the code: now it's very compact, thanks to libraries apply and yall.

nvalue(1, [_]).
nvalue(C, [V|Vs]) :-
    maplist({V}/[U,Eq]>>(Eq#<==>V#=U), Vs, Es),
    sum(Es, #=, E),
    E #= 0 #/\ C #= R+1 #\/ E #> 0 #/\ C #= R,
    nvalue(R, Vs).

old answer, buggy

my naive attempt, based on reification:

% nvalue(?N, +Variables)
nvalue(N, Vs) :-
    nvalues(Vs, [], VRs),
    sum(VRs, #=, N).

nvalues([], Acc, Acc).
nvalues([V|Vs], Acc, VRs) :-
    nvalues_(V, Vs, Acc, Upd),
    nvalues(Vs, Upd, VRs).

nvalues_(_V, [], Acc, Acc).
nvalues_(V, [U|Vs], Acc, Upd) :-
    V #\= U #<==> D,
    nvalues_(V, Vs, [D|Acc], Upd).

running your example query:

?- length(Vs, 3), Vs ins 1..3, nvalue(2, Vs), label(Vs).
Vs = [1, 1, 2] ;
Vs = [1, 1, 3] ;
Vs = [1, 2, 1] ;
Vs = [1, 2, 2] ;
Vs = [1, 3, 1] ;
Vs = [1, 3, 3] ;
Vs = [2, 1, 1] ;
Vs = [2, 1, 2] ;
Vs = [2, 2, 1] ;
Vs = [2, 2, 3] ;
Vs = [2, 3, 2] ;
Vs = [2, 3, 3] ;
Vs = [3, 1, 1] ;
Vs = [3, 1, 3] ;
Vs = [3, 2, 2] ;
Vs = [3, 2, 3] ;
Vs = [3, 3, 1] ;
Vs = [3, 3, 2].

edit

my code was a bit pedantic, of course could be more compact (and clear ?):

nvalue(N, Vs) :-
    bagof(D, X^H^T^V^(append(X, [H|T], Vs), member(V, T), V #\= H #<==> D), VRs),
    sum(VRs, #=, N).

note that findall/3 will not work, since the copy of reified variable D would lose the posted constraints.

false
  • 10,264
  • 13
  • 101
  • 209
CapelliC
  • 59,646
  • 5
  • 47
  • 90
  • 2
    Not correct: your code succeeds for `nvalue(3, [1,1,1,2])`, for example. – jschimpf Jan 07 '17 at 14:21
  • 1
    Nice rewrite! Note that you can still simplify by turning the summation in `count_equals/3` into a conjunction using `E#<==>E1#/\(V#\=U)`, and simply summing the resulting booleans in the outer loop. – jschimpf Jan 08 '17 at 15:39
  • @jschimpf: thanks again. I've followed your hint, but simplified... hope I got it right ... at least, it seems ok. – CapelliC Jan 08 '17 at 23:12
  • 1
    Not exactly what I had in mind. You don't need to compute the sum `E` of the satisfied equalities, you only have to compute a boolean `D` that is true if all equalities are false. Then you can simplify `E #= 0 #/\ C #= R+1 #\/ E #> 0 #/\ C #= R` to `C #= R+D`. – jschimpf Jan 09 '17 at 00:10