constrain list length in CLP (Prolog)

95 views Asked by At

I have an optimisation problem, where I want to minimse the length of a list which I have yet to construct. Language here is Prolog, using swipl's clpfd library.

Something along the lines of pred(L), length(L,N), labeling([min(N)],[....]).

Problem is: length/2 already sets the length, even if L is a variable, so this ain't CLP optimisation after that. So, can I somehow constrain the length of L to be N without instantiating N? Perhaps using reification if I know an upper bound for N (which I do) - but that does sound awfully complicated? TBH I have similar issues stopping with my pred/1 thing to commit to a particular list length, but I was just primarily curious about that length thing.

1

There are 1 answers

0
brebs On

Can delay using when:

list_min_max_len(MinLen, MaxLen, Lst) :-
    when(nonvar(MinLen), (
        MinLen @>= 0,
        ensure_min_len(MinLen, Lst)
    )),
    when((nonvar(MinLen), nonvar(MaxLen)), (
        MaxLen @>= MinLen
    )),
    when(nonvar(MaxLen),
        max_len_freeze(Lst, 0, MaxLen)
    ).

max_len_freeze([], Upto, MaxLen) :-
    Upto == MaxLen,
    % At max len, so avoid [H|T]
    !.  
max_len_freeze([], Upto, MaxLen) :-
    Upto @< MaxLen.
max_len_freeze([_|T], Upto, MaxLen) :-
    Upto1 is Upto + 1,
    compare(Comp, MaxLen, Upto1),
    max_len_freeze_next_(Comp, T, Upto1, MaxLen).
    
max_len_freeze_next_(=, [], Upto, MaxLen) :-
    max_len_freeze([], Upto, MaxLen).
max_len_freeze_next_(>, T, Upto, MaxLen) :-
    when(nonvar(T), max_len_freeze(T, Upto, MaxLen)).

ensure_min_len(MinLen, Lst) :-
    compare(Comp, MinLen, 0),
    ensure_min_len_(Comp, Lst, MinLen).

ensure_min_len_(=, _, 0).
ensure_min_len_(>, [_|T], MinLen) :-
    MinLen0 is MinLen - 1,
    compare(Comp, MinLen0, 0),
    ensure_min_len_(Comp, T, MinLen0).

Results in swi-prolog:

?- list_min_max_len(Min, Max, L), L = [a,b,c|T], Max = 3, Min = 3.
L = [a, b, c],
Min = Max, Max = 3,
T = [].

?- list_min_max_len(Min, Max, L), L = [a,b,c|T], Min = 5.
L = [a, b, c, _A, _B|_C],
Min = 5,
T = [_A, _B|_C],
when(nonvar(Max), max_len_freeze([a, b, c, _A, _B|_C], 0, Max)),
when(nonvar(Max), Max@>=5).

% Prevents length/2 from going to infinity, if MaxLen is specified first
?- list_min_max_len(Min, Max, L), L = [a,b,c|T], Min = 2, Max = 5, length(L, Len).
L = [a, b, c],
Min = 2,
Max = 5,
T = [],
Len = 3 ;
L = [a, b, c, _A],
Min = 2,
Max = 5,
T = [_A],
Len = 4 ;
L = [a, b, c, _A, _B],
Min = 2,
Max = Len, Len = 5,
T = [_A, _B].

% Beware - can't magically rein in length/2
?- list_min_max_len(Min, Max, L), Min = 2, L = [a,b,c|T], length(L, Len), Max = 3.
L = [a, b, c],
Min = 2,
Max = Len, Len = 3,
T = [] ;
<length/2 goes to infinity>