listprologinstantiation-error

Insert into open-ended list without binding its tail variable


Is it possible to solve the following problem in Prolog?

Let A and B be lists of numbers and let N be a number. It is known that B is sorted decreasingly. Check if N can be inserted into A so that the result is B, but do not bind any variable that occurs as a tail in either A nor B.

For example

?- insertable(34, [78, 72, 11 | Z], [78, 72, 34, 11 | Z]).
true. 

?- insertable(34, [78, 72, 11 | Z], L).
L = [78, 72, 34, 11 | Z].

Can anyone help me? :)

EDIT 1: This is what I came up with.

insertable(X, List1, List2):- select(X, List2, List1), sorted(List2).

sorted([]).
sorted([_]).
sorted([X, Y | Rest]) :-
  X > Y,
  sorted([Y | Rest]).

However, even though it works as expected when the arguments are fully instantiated, it binds variables located in tails:

?- insertable(11, [5, 3, 2], [11, 5, 3, 2]).
true .

?- insertable(11, [5, 3, 2 | X], [11, 5, 3, 2 | X] ).
X = [] .

?- insertable(11, [5, 3, 2 | X], L ).
X = [],
L = [11, 5, 3, 2] .

EDIT 2: Here's another approach that I tried.

in(X, [], [X]).
in(X, [Head | Tail1], [Head | Tail2]) :-
  X =< Head,
  in(X, Tail1, Tail2).
in(X, [Head | Tail], [X, Head | Tail]) :-
  X > Head.

The problem is still there:

?- in(1, [3, 2], [3, 2, 1]).
true ;
false.

?- in(1, [3, 2], L).
L = [3, 2, 1] ;
false.

?- in(1, [3, 2 | X], L).
X = [],
L = [3, 2, 1] ;
ERROR: =</2: Arguments are not sufficiently instantiated
   Exception: (9) in(1, _G8394089, _G8394190) ? abort
% Execution Aborted
?- in(1, [3, 2 | X], [3, 2, 1 | X]).
X = [] ;
X = [1] ;
X = [1, 1] ;
X = [1, 1, 1] ;
X = [1, 1, 1, 1] ;
X = [1, 1, 1, 1, 1] ;
X = [1, 1, 1, 1, 1, 1] ;
X = [1, 1, 1, 1, 1, 1, 1] .

Solution

  • The trick for this exercise are the metalogical predicates var/1 and nonvar/1 which are true if the argument is a variable or not (also have a look at ground/1, atom/1 and integer/1). To make the difference is a little clumsy because we need to keep the list L1 in the head and unify after we know it is a variable or not.

    What also might have confused you is the error message of the arithmetic comparison. For that to work, both arguments must be ground. When you don't test for non-var of the tail, the unification automatically instantiates the tail with [Head|Tail1]. That always leads to a comparison number <= Head which leads to the error you had.

    The following code assumes you would also like to insert into lists that have a closed tail. If not, the first rule needs to be removed.

    in(X, L1, [X]) :- % terminal case for empty list (i.e. tail is not a variable)
        nonvar(L1),
        L1 = [].
    in(X, Xs, [X | Xs]) :- % terminal case if the tail is a variable
        var(Xs).
    in(X, L1, [N | Zs]) :- % recursive case X =< N
        nonvar(L1),
        L1 = [N | Ys],
        X =< N,
        in(X, Ys, Zs).
    in(X, L1, [X,  N | Ys]) :- % recursive case X > N
        nonvar(L1),
        L1 = [N | Ys],
        X > N.
    

    When we test we can insert 1 in front of a variable tail (after the first result, there are still paths to test but all fail, leading to the false after continuing the query):

    ?- in(1,Xs,Ys).
    Ys = [1|Xs] ;
    false.
    

    Also, the inserted element must be 1, so this one should fail:

    ?- in(1,Xs,[2|Ys]).
    false.
    

    It seems the recursion properly propagates to the end:

    ?- in(1,[3, 2 | Xs], Zs).
    Zs = [3, 2, 1|Xs] ;
    false.
    

    Inserting in the middle also works:

    ?- in(2,[3,1 |Xs],Zs).
    Zs = [3, 2, 1|Xs].
    

    And finally the test case you tried to solve before:

    ?- in(34, [78, 72, 11 | Z], [78, 72, 34, 11 | Z]).
    true ;
    false.
    

    What still doesn't work is if you have variables occurring in your list:

    ?- in(2,[3,A,1|Xs],Zs).
    ERROR: Arguments are not sufficiently instantiated
    ERROR: In:
    ERROR:   [10] 2=<_8374
    ERROR:    [9] in(2,[_8406,1|_8414],[_8418|_8420]) at ./prolog/so-3.pl:9
    ERROR:    [8] in(2,[3,_8458|...],[3,_8470|_8472]) at ./prolog/so-3.pl:10
    ERROR:    [7] <user>
       Exception: (9) in(2, [_7488, 1|_7496], _7820) ? a
    

    An easy way out would be to guard the comparison with an integer(N) to get

    ?- in(2,[3,A,1|Xs],Zs).
    false.
    

    But then we should also guard against the inserted element being non-integer and the lists having decending integers followed with a variable tail. Alternatively, we could throw a better exception in these cases.