A typical code example of list processing in Prolog is append:
append([], Ys, Ys).
append([X | Xs], Ys, [X | Zs]) :- append(Xs, Ys, Zs).
My question is whether this program is tail recursive or not. I guess not from my experience in functional languages. However, I find it more difficult to judge for Prolog programs. It seems to we have to take unification into consideration.
Yes, your (and hence the Prolog "standard" version of) append/3 is tail-recursive. You can see this easily because the final goal is a call to append/3 itself. Notice that a typical implementation of append in functional languages is not tail recursive, because the final call is an operation equivalent to cons in Lisp, corresponding for example to:
lisp_append([], Ys, Ys).
lisp_append([X|Xs], Ys, Zs) :-
lisp_append(Xs, Ys, Zs0),
Zs = [X|Zs0].
Example query, yielding a local stack overflow because tail call optimization cannot be applied:
?- length(Ls, 10_000_000), lisp_append(Ls, [], _).
ERROR: Out of local stack
Whereas your natural Prolog version of append/3 works:
?- length(Ls, 10_000_000), append(Ls, [], _).
Ls = [_G8, _G11, _G14, _G17, _G20, _G23, _G26, _G29, _G32|...].
Notice that more predicates are naturally tail recursive in Prolog than in functional languages, due to the power of unification which lets you pull the description of partial results before a tail call. +1 for a good question.
No, your code is NOT tail-recursive.
Tail-recursive means that at the bottom of the recursion you get the answer that you ask for at the beginning directly.
If you trace your code, for exampleappend([1,2,3],[a,b,c],Out), your get:
Call:append([1, 2, 3], [a, b, c], _G4702)
Call:append([2, 3], [a, b, c], _G4756)
Call:append([3], [a, b, c], _G4759)
Call:append([], [a, b, c], _G4762)
Exit:append([], [a, b, c], [a, b, c])
Exit:append([3], [a, b, c], [3, a, b, c])
Exit:append([2, 3], [a, b, c], [2, 3, a, b, c])
Exit:append([1, 2, 3], [a, b, c], [1, 2, 3, a, b, c])
The values of the variables(_G4762,_G4759,_G4756) are passed up to _G4702, and _G4702 is the answer.
We may have a tail-recursive version of append:
ap_tail_r([H|T],B,Ac,Out):-
ap_tail_r(T,B,[H|Ac],Out).
ap_tail_r([],B,[H|Ac],Out):-
ap_tail_r([],[H|B],Ac,Out).
ap_tail_r([],Out,[],Out).
Let trace again ap_tail_r([1,2,3],[a,b,c],[],Out):
Call:ap_tail_r([1, 2, 3], [a, b, c], [], _G4786)
Call:ap_tail_r([2, 3], [a, b, c], [1], _G4786)
Call:ap_tail_r([3], [a, b, c], [2, 1], _G4786)
Call:ap_tail_r([], [a, b, c], [3, 2, 1], _G4786)
Call:ap_tail_r([], [3, a, b, c], [2, 1], _G4786)
Call:ap_tail_r([], [2, 3, a, b, c], [1], _G4786)
Call:ap_tail_r([], [1, 2, 3, a, b, c], [], _G4786)
Exit:ap_tail_r([], [1, 2, 3, a, b, c], [], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [2, 3, a, b, c], [1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [3, a, b, c], [2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [a, b, c], [3, 2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([3], [a, b, c], [2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([2, 3], [a, b, c], [1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([1, 2, 3], [a, b, c], [], [1, 2, 3, a, b, c])
The only variable that we are keeping tract of is _G4786, which is the answer that we look for at the first place.
What exactly the tail-recursive code does is: a. reverse the first part, b. put the reversed first part to the second part head by head, c. when the reserved first part is empty, the updated second part is the appended result.
Related
I'm a newbie in Prolog, I have this question.
From this fact need getting every single node from this nodelist, how I can do?
nodelist([[a,[2,3],[b,d]],[b,[5,1],[a,c,d]],[c,[3,2],[b,d]]).
where nodelist([[node,[coordinate_node],[neighbours]]])
I've tried
node(Nodelist,Node):- nodelist(Nodelist), findall(Node,Nodelist(Nodes),Nodes).
syntax error operator expected
I show you two ways to do the job :
First way, "functionnal design" because you work with every element of the list of nodes
:- use_module(library(lambda)).
nodelist([[a,[2,3],[b,d]],[b,[5,1],[a,c,d]],[c,[3,2],[b,d]]]).
fetch_nodes(In, Out) :-
foldl(\X^Y^Z^(X = [_, _, Nodes], union(Y,Nodes, Z)), In, [], Out).
example
?- nodelist(L), fetch_nodes(L, Nodes).
L = [[a, [2, 3], [b, d]], [b, [5, 1], [a, c, d]], [c, [3, 2], [b, d]]],
Nodes = [a, c, b, d].
The second way is more "prologish", aggregate_all extends findall :
nodes(Nodelist,Nodes):-
aggregate_all(set(Node), (member([_, _, L], Nodelist), member(Node, L)),Nodes).
PS You can find details for library lambda, foldl, union and aggregate_all on the site of SWI-Prolog
Here is a complete program. Note that I had to fix your definition of nodelist/1.
nodelist([[a,[2,3],[b,d]],[b,[5,1],[a,c,d]],[c,[3,2],[b,d]]]).
node(X) :-
nodelist(L),
member([X,_,_], L).
nodes(Xs) :-
nodelist(L),
nodes(L, Xs).
nodes([], []).
nodes([[X,_,_]|L], [X|Xs]) :-
nodes(L, Xs).
and now:
?- node(X).
X = a ;
X = b ;
X = c.
?- nodes(Xs).
Xs = [a, b, c].
I have the following predicate which I have written for recognising when two lists are the same except the two elements at indices I1 and I2 are swapped:
swapped(I1, I2, List, NewList) :-
% The lists are the same length and the two indices are swapped.
same_length(List, NewList),
nth0(I1, List, V1), nth0(I2, List, V2),
nth0(I1, NewList, V2), nth0(I2, NewList, V1),
% All the other indices remain the same.
proper_length(List, Length), Lim is Length - 1,
numlist(0, Lim, Indices),
forall((member(I, Indices), I \= I1, I \= I2),
(nth0(I, List, V), nth0(I, NewList, V))).
The following swipl output demonstrates my issue:
?- swapped(0, 1, [1,2,3], L).
L = [2, 1, _G5035].
?- swapped(0, 1, [1,2,3], [2,1,3]).
true.
?- swapped(0, 1, [1,2,3], [2,1,4]).
false.
Why does it return a variable for the third element rather than just 3, given that it can recognise that 3 is the only correct term? These are the last four parts of the trace where the unification happens and is then forgotten:
Call: (10) lists:nth0(2, [2, 1, _G6121], 3) ? creep
Exit: (10) lists:nth0(2, [2, 1, 3], 3) ? creep
^ Exit: (8) forall(user: (member(_G6145, [0, 1, 2]), _G6145\=0, _G6145\=1), user: (nth0(_G6145, [1, 2, 3], _G6162), nth0(_G6145, [2, 1, _G6121], _G6162))) ? creep
Exit: (7) swapped(0, 1, [1, 2, 3], [2, 1, _G6121]) ? creep
I don't doubt there's a better way to swap two elements (perhaps recursively) but I would like to know why this is happening and how to fix it; I'm clearly lacking in some Prolog knowledge.
Thanks!
forall/2 is a so called 'failure driven loop'. Then instantiations are undone between cycles.
In SWI-Prolog, there is foreach/2, that fixes the problem with your first query.
...
numlist(0, Lim, Indices),
foreach((member(I, Indices), I \= I1, I \= I2),
(nth0(I, List, V), nth0(I, NewList, V))).
Test:
?- swapped(0, 1, [1,2,3], L).
L = [2, 1, 3].
In SWI-Prolog, sometime the better way to understand a builtin is to inspect the source. You can see that foreach/2 is a fairly complicated predicate... from swipl prompt, try ?- edit(foreach)., or follow the source link from the doc page (the circled :-).
I'm trying to create a function that would remove all the occurrences of an element form a list. I saw that some solutions have already been mentioned. But I'm trying a different approach.
I am basically copying the elements in a new list if they are not equal to what I want to delete.
remove(X, [], A, A):-!.
remove(X1, [X1|T1], A, R):-
remove(X1, T1, A, R).
remove(X2, [H2|T2], A2, R2):-
remove(X2, T2, [H2 |A2], R2).
The first answer is the result I expect, but then it keeps providing more possibilities:
?- remove(x, [x, b,c,x,x], [], Result).
Result = [c, b] ;
Result = [x, c, b] ;
Result = [x, c, b] ;
Result = [x, x, c, b] ;
Result = [c, b, x] ;
Result = [x, c, b, x] ;
Result = [x, c, b, x] ;
Result = [x, x, c, b, x].
[added after edit] Why does it keep providing answers?
Is there a way to have it stop at the first result?
Edit: I also just realized that this is also reversing my list :(
The problem is in the clause:
remove(X2, [H2|T2], A2, R2):-
remove(X2, T2, [H2 |A2], R2).
You need to state explicitly that X2 differs from H2. You can do that by using dif/2:
remove(X2, [H2|T2], A2, R2):-
dif(X2,H2),
remove(X2, T2, [H2 |A2], R2).
If you don't state that X2,H2 are different Prolog will try both cases which will lead to keep or throw element H2.
Short answer: if you write remove(X2, [H2|T2], A2, R2), that does not mean that X2 and H2 are different, so you need to prevent Prolog from taking that branch. A fundamental aspect of Prolog is that it automatically performs backtracking. So the fact that one clause "fires" does not prevents it from other clauses to fire.
A fast fix can be to add a cut to the second clause:
remove(X, [], A, A) :-
!.
remove(X1, [X1|T1], A, R):-
!, % a cut
remove(X1, T1, A, R).
remove(X2, [H2|T2], A2, R2):-
remove(X2, T2, [H2 |A2], R2).
So in case X2 and H2 can be unified, Prolog will take the second clause, and then the cut will prevent Prolog taking the third clause.
Another important aspect is that your program reverses the list. We can fix this by dropping the accumulator here (you can also work with difference lists as an accumulator, but this will introduce too much complexity for now):
remove(_, [], []).
remove(X1, [X1|T1], R):-
!,
remove(X1, T1, R).
remove(X2, [H2|T2], [H2|R2]):-
remove(X2, T2, R2).
And now you can use it to remove elements from a list that might be the same: note that Prolog performs unification, so it will also remove free variables, or partially ungrounded ones that can unify.
But: cuts are dangerous and will usually result in impure predicates. The idea of Prolog is that you can use predicates in multiple directions. For instance you can generate all possible lists with such that the outcome after removing 3 would be [1,4,2,5]. In order to do this, we can add a constraint dif/2:
remove(_, [], []).
remove(X2, [H2|T2], [H2|R2]) :-
dif(X2, H2),
remove(X2, T2, R2).
remove(X1, [X1|T1], R):-
remove(X1, T1, R).
we also remove the cut here.
Now we can query it with different tasks:
remove all 4s from the list [1,4,2,4,4,5]:
?- remove(4,[1,4,2,4,4,5],X).
X = [1, 2, 5] ;
false.
remove an element A from the list:
?- remove(A,[1,4,2,4,4,5],X).
X = [1, 4, 2, 4, 4, 5],
dif(A, 5),
dif(A, 4),
dif(A, 4),
dif(A, 2),
dif(A, 4),
dif(A, 1) ;
A = 5,
X = [1, 4, 2, 4, 4] ;
A = 2,
X = [1, 4, 4, 4, 5] ;
A = 4,
X = [1, 2, 5] ;
A = 1,
X = [4, 2, 4, 4, 5] ;
false.
generate all lists that could be the input of the removal such that the result is [1,4,2,5] (infinite amount of answers):
?- remove(A,L,[1,4,2,5]).
L = [1, 4, 2, 5],
dif(A, 5),
dif(A, 2),
dif(A, 4),
dif(A, 1) ;
L = [1, 4, 2, 5, A],
dif(A, 5),
dif(A, 2),
dif(A, 4),
dif(A, 1) ;
L = [1, 4, 2, 5, A, A],
dif(A, 5),
dif(A, 2),
dif(A, 4),
dif(A, 1) ;
L = [1, 4, 2, 5, A, A, A],
dif(A, 5),
dif(A, 2),
dif(A, 4),
dif(A, 1) ;
L = [1, 4, 2, 5, A, A, A, A],
dif(A, 5),
dif(A, 2),
dif(A, 4),
dif(A, 1)
...
what element is removed from the list?
?- remove(A,[1,4,3,2,5],[1,4,2,5]).
A = 3 ;
false.
?- remove(A,[1,3,4,3,2,5],[1,4,2,5]).
A = 3 ;
false.
can we obtain a list [1,4,2,5] from removing only one sort of value from [1,9,4,3,2,5]? (No!)
?- remove(A,[1,9,4,3,2,5],[1,4,2,5]).
false.
I am completely new to Prolog, but I have to do this for a homework. I have tried something like this
delete(_,[],[]).
delete([X,Y,Z],[X,Y,Z|List],Temp) :-
reverse(Temp,List).
But can't figure out, how to implement second delete from reversed list. Maybe I'm doing it all wrong, I'm lost, thanks for answers.
Using append:
delete([A, B, C | End], Middle, [A, B, C, X, Y, Z]) :-
append(Middle, [X, Y, Z], End).
Test run:
?- delete([1,2,3,4,5,6,7,8], L1, L2).
L1 = [4, 5],
L2 = [1, 2, 3, 6, 7, 8]
?- delete([1,2,3], L1, L2).
false.
just need a simple explanation.. trying to piece everything still together here.
lastitem([X|Xs],Out) :- lastitem(Xs,Out).
here is trace on: lastitem([a,b,c],X).
[trace] 8 ?- lastitem([a,b,c],X).
Call: (6) lastitem([a, b, c], _G536) ? creep
Call: (7) lastitem([b, c], _G536) ? creep
Call: (8) lastitem([c], _G536) ? creep
Exit: (8) lastitem([c], c) ? creep
Exit: (7) lastitem([b, c], c) ? creep
step 1 says if lastitem(something,somethign) exists then listem([X|Xs],Out].. so A is cut out.
step 2-3 does the same.. but w/ B and C.
now question is what happens w/ the empty list in step 4?
why does the empty list not fulfill lastitem(Xs,Out)? or am I solving incorrectly?
Also a verbal explanation of backtracing would help.. because in append I'm really getting twisted. Append has no goals to solve between steps.. yet reverse does not.. nor does my answer above.. if you trace it you can see the X variable is always the same in reverse or this example. in append it changes.
append([],L,L).
append([H|T],L2,[H|L3]) :- append(T,L2,L3).
append([a, b, c], [1, 2, 3], _G518) % <-- variable L3 continues to change
append([b, c], [1, 2, 3], _G587) % <-- same
append([c], [1, 2, 3], _G590) % < -- same
append([], [1, 2, 3], _G593) % <-- same
append([], [1, 2, 3], [1, 2, 3])
append([c], [1, 2, 3], [c, 1, 2, 3])
append([b, c], [1, 2, 3], [b, c, 1, 2, 3])
append([a, b, c], [1, 2, 3], [a, b, c, 1, 2, 3])
X = [a, b, c, 1, 2, 3]
Just as you, I'm confused by the absence of a base case in lastitem. Are you sure it wasn't actually defined as
lastitem([X|[]], X).
lastitem([X|Xs],Out):- lastitem(Xs,Out).
or something similar?
As for all the backtraces, try to not think too imperatively when looking at Prolog code.
For example, append can be "translated" to a more usual functional definition:
function append(xs, ys) =
if xs is [] then
return ys
else
let [H|L] = xs
return [H | append(L, ys)]
Unterstanding this goes a long way to understanding the Prolog version :)
In lastitem([X|Xs],Out) :- lastitem(Xs,Out)., on both sides the second argument is Out, so it has to stay the same.
In append([H|T],L2,[H|L3]) :- append(T,L2,L3)., the third argument on the left side is [H|L3], but on the right side it's L3, so when you call append, you have a “variable” for [H|L3], but the variable for L3 has to be different. The variable names like _G536 are global, so when they represent different things, they have to be different.
(Sorry for imprecise terminology, I haven't worked with Prolog for a while.)