3

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] .
mat
  • 40,498
  • 3
  • 51
  • 78
Milos
  • 518
  • 7
  • 22
  • 4
    I think if you just try implementing it without worrying too much about the property you're trying to conserve, you'll find it just falls out naturally. What have you tried? – Daniel Lyons Nov 29 '18 at 06:19
  • Hi, @DanielLyons . I added the code that I managed to write. Could you, please, take a look at it? – Milos Nov 29 '18 at 12:20
  • do your lists always have an uninstantiated variable as their tail? should `insert( 10, [11 | Z], X)` succeed or fail? – Will Ness Nov 29 '18 at 18:54
  • @WillNess no, they don't always have an uninstantiated variable as their tail. `insert(10, [11|Z], X)` should succeed with unifying `X` with `[11, 10 | Z]`. – Milos Nov 29 '18 at 19:12
  • but you said the tail shouldn't be altered. (??) did you mean that the list should remain open, but is allowed to grow at tail as well? – Will Ness Nov 29 '18 at 19:14
  • Well, `Z` in `[11|Z]` is not to be altered, but `X` is, in this particular example, supposed to get unified. Thanks for the link, I'll take a look at it. – Milos Nov 29 '18 at 19:20

1 Answers1

2

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.

lambda.xy.x
  • 4,918
  • 24
  • 35
  • How can I define an `out` function that removes the item in the descending list, with open ended properties. – eguneys Jun 20 '22 at 06:56
  • I'm not sure if I understand the question - you want to a predicate to delete an element from a difference list? It might be easier to put this into its own question instead of amending this one. – lambda.xy.x Jun 20 '22 at 10:20