Prolog list and recursion in finding a graph path [duplicate] - list

Many predicates define some kind of an acyclic path built from edges defined via a binary relation, quite similarly to defining transitive closure. A generic definition is thus called for.
Note that the notions defined in graph theory do not readily match what is commonly expected. Most notably, we are not interested in the edges' names.
Worse, also graph theory has changed a bit, introducing the notion of walk, noting
Traditionally, a path referred to what is now usually known as an open walk. Nowadays, when stated without any qualification, a path is usually understood to be simple, meaning that no vertices (and thus no edges) are repeated. (The term chain has also been used to refer to a walk in which all vertices and edges are distinct.)
So my question is: How to name and define this functionality?
What I have done so far is to define:
path(Rel_2, Path, X0,X)
The first argument has to be the continuation of the relation which is an incomplete goal that lacks two further arguments. Then comes either the Path or the pair of vertices.
Example usage
n(a, b).
n(b, c).
n(b, a).
?- path(n,Xs, a,X).
Xs = [a], X = a
; Xs = [a, b], X = b
; Xs = [a, b, c], X = c
; false.
Implementation
:- meta_predicate(path(2,?,?,?)).
:- meta_predicate(path(2,?,?,?,+)).
path(R_2, [X0|Ys], X0,X) :-
path(R_2, Ys, X0,X, [X0]).
path(_R_2, [], X,X, _).
path(R_2, [X1|Ys], X0,X, Xs) :-
call(R_2, X0,X1),
non_member(X1, Xs),
path(R_2, Ys, X1,X, [X1|Xs]).
non_member(_E, []).
non_member(E, [X|Xs]) :-
dif(E,X),
non_member(E, Xs).

How about defining path/4 like this?
path(R_2, Xs, A,Z) :- % A path `Xs` from `A` to `Z` is ...
walk(R_2, Xs, A,Z), % ... a walk `Xs` from `A` to `Z` ...
all_dif(Xs). % ... with no duplicates in `Xs`.
To aid universal termination, we swap the two goals in above conjunction ...
path(R_2, Xs, A,Z) :-
all_dif(Xs), % enforce disequality ASAP
walk(R_2, Xs, A,Z).
... and use the following lazy implementation of all_dif/1:
all_dif(Xs) :- % enforce pairwise term inequality
freeze(Xs, all_dif_aux(Xs,[])). % (may be delayed)
all_dif_aux([], _).
all_dif_aux([E|Es], Vs) :-
maplist(dif(E), Vs), % is never delayed
freeze(Es, all_dif_aux(Es,[E|Vs])). % (may be delayed)
walk/4 is defined like path/4 and path/5 given by the OP:
:- meta_predicate walk(2, ?, ?, ?).
walk(R_2, [X0|Xs], X0,X) :-
walk_from_to_step(Xs, X0,X, R_2).
:- meta_predicate walk_from_to_step(?, ?, ?, 2).
walk_from_to_step([], X,X, _).
walk_from_to_step([X1|Xs], X0,X, R_2) :-
call(R_2, X0,X1),
walk_from_to_step(Xs, X1,X, R_2).
IMO above path/4 is simpler and more approachable, particularly for novices. Would you concur?

I want to focus on naming the predicate.
Unlike maplist/2,
the argument order isn't of primary importance here.
The predicate name should make the meaning of the respective arguments clear.
So far, I like path_from_to_edges best, but it has its pros and cons, too.
path_from_to_edges(Path,From,To,Edges_2) :-
path(Edges_2,Path,From,To).
Let's pick it apart:
pro: path is a noun, it cannot be mis-read a verb. To me, a list of vertices is implied.
pro: from stands for a vertex, and so does to.
con: edges is somewhat vague, but using lambdas here is the most versatile choice.
con: According to Wikipedia, a path is a trail in which all vertices (except possibly the first and last) are distinct. So that would need to be clarified in the description.
Using lambdas for a lists of neighbor vertices Ess:
?- Ess = [a-[b],b-[c,a]],
From = a,
path_from_to_edges(Path,From,To,\X^Y^(member(X-X_neibs,Ess),member(Y,X_neibs))).
Ess = [a-[b],b-[c,a]], From = a, To = a, Path = [a]
; Ess = [a-[b],b-[c,a]], From = a, To = b, Path = [a,b]
; Ess = [a-[b],b-[c,a]], From = a, To = c, Path = [a,b,c]
; false.
Edit 2015-06-02
Another shot at better naming! This leans more on the side of maplist/2...
graph_path_from_to(P_2,Path,From,To) :-
path(P_2,Path,From,To).
Here, graph, of course, is a noun, not a verb.
Regarding the meaning of "path": paths definitely should allow From=To and not exclude that by default (with pairwise term inequalities). It is easy to exclude this with an additional dif(From,To) goal, but not the other way round.

I do not see the reason to define in path/4 the arguments "start node" and "end node". It seems that a simple path/2 with the rule and the list of nodes must be enough.
If the user wants a list starting with some node (by example, 'a'), he can query the statement as: path( some_rule, ['a'|Q] ).
A user could, by example, request for path that have length 10 in the way: length(P,10), path( some_rule, P).
* Addendum 1 *
Some utility goals can be easily added, but they are not the main subject. Example, path/3 with start node is:
path( some_rule, [start|Q], start ) :-
path ( some_rule, [start|Q ] ).
* Addendum 2 *
Addition of last node as argument could give the false idea that this argument drives the algorithm, but it doesn't. Assume by example:
n(a, b).
n(a, c).
n(a, d).
and trace algorithm execution for the query:
[trace] ?- path( n, P, X, d ).
Call: (6) path(n, _G1025, _G1026, d) ? creep
Call: (7) path(n, _G1107, _G1026, d, [_G1026]) ? creep
Exit: (7) path(n, [], d, d, [d]) ? creep
Exit: (6) path(n, [d], d, d) ? creep
P = [d],
X = d ;
Redo: (7) path(n, _G1107, _G1026, d, [_G1026]) ? creep
Call: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, b) ? creep
Call: (8) non_member(b, [a]) ? creep
Call: (9) dif:dif(b, a) ? creep
Exit: (9) dif:dif(b, a) ? creep
Call: (9) non_member(b, []) ? creep
Exit: (9) non_member(b, []) ? creep
Exit: (8) non_member(b, [a]) ? creep
Call: (8) path(n, _G1113, b, d, [b, a]) ? creep
Call: (9) n(b, _G1118) ? creep
Fail: (9) n(b, _G1118) ? creep
Fail: (8) path(n, _G1113, b, d, [b, a]) ? creep
Redo: (9) non_member(b, []) ? creep
Fail: (9) non_member(b, []) ? creep
Fail: (8) non_member(b, [a]) ? creep
Redo: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, c) ? creep
Call: (8) non_member(c, [a]) ? creep
Call: (9) dif:dif(c, a) ? creep
Exit: (9) dif:dif(c, a) ? creep
Call: (9) non_member(c, []) ? creep
Exit: (9) non_member(c, []) ? creep
Exit: (8) non_member(c, [a]) ? creep
Call: (8) path(n, _G1113, c, d, [c, a]) ? creep
Call: (9) n(c, _G1118) ? creep
Fail: (9) n(c, _G1118) ? creep
Fail: (8) path(n, _G1113, c, d, [c, a]) ? creep
Redo: (9) non_member(c, []) ? creep
Fail: (9) non_member(c, []) ? creep
Fail: (8) non_member(c, [a]) ? creep
Redo: (8) n(_G1026, _G1112) ? creep
Exit: (8) n(a, d) ? creep
Call: (8) non_member(d, [a]) ? creep
Call: (9) dif:dif(d, a) ? creep
Exit: (9) dif:dif(d, a) ? creep
Call: (9) non_member(d, []) ? creep
Exit: (9) non_member(d, []) ? creep
Exit: (8) non_member(d, [a]) ? creep
Call: (8) path(n, _G1113, d, d, [d, a]) ? creep
Exit: (8) path(n, [], d, d, [d, a]) ? creep
Exit: (7) path(n, [d], a, d, [a]) ? creep
Exit: (6) path(n, [a, d], a, d) ? creep
P = [a, d],
X = a .
as you can see, in this case algorithm fails to brute force.
For this reason, if algorithm is not improved, I suggest do not add "end node" as "path" argument.

Related

Prolog - Split a list in two halves, reversing the first half

I am asked to take a list of letters into two lists that are either equal in size (even sized original list I guess) or one is larger than the other by one element (odd sized list), and reverse the first one while I'm at it.
Query and output example:
?- dividelist2([a,b,c,d,e,f], L1,L2).
L1 = [c,b,a]
L2 = [d,e,f]
?- dividelist2([a,b,c,d,e], L1,L2).
L1 = [c,b,a]
L2 = [d,e]
% OR
L1 = [b,a]
L2 = [c,d,e]
I've reached this solution, but I have a feeling something is wrong with it. Please let me know if I'm missing something... How do I get the last output? (two options for output, I mean)
dividelist2(L, X, B) :-
( append(A, B, L),
length(A, O),
length(B, N),
( (O-1) =:= N
; (O+1) =:= N
; O =:= N
),
!
), reverse(A, X).
reverse(List, Rev) :-
reverse(List, Rev, []).
reverse([], L, L).
reverse([H|T], L, SoFar) :-
reverse(T, L, [H|SoFar]).
Thanks in advance!
You're on the right track:
splitflip(In, Out1, Out2) :-
length(In, L), % length of the list
FL is L//2, % integer division, so half the length, Out1 will be 1 shorter than Out2 if L is odd
( \+ (FL*2 =:= L), % is odd
FLP is FL + 1 % odd case, choicepoint left
; FLP = FL % odd and even case, no choicepoint left
), % the one place we need a choicepoint for the odd length case of an odd input, note order prunes choicepoint for even case
length(FirstHalf, FLP), % generate an list of vars to the right length
append(FirstHalf, Out2, In), % unify with the first half and generate second half of correct length
reverse(FirstHalf, Out1). % do the reverse
Just need to reorder things a bit and use integer division.
Thanks #WillNess, I have a full solution here using the tortoise/hare trick. The idea behind that trick is that you are traversing the list simultaneously, with the tortoise going element by element and the hare going by two elements at a time. When the hare list is empty, you know you're at the middle of the list. This inlines a common accumulator pattern for reversing a list in one pass, which is how we produce the first half of the result. The second half of the result is just the remainder of the list--which is to say, the tortoise list.
divide(List, Left, Right) :-
reverse(List, List, [], Left, Right).
% reverse(Tortoise, Hare, ReverseAccumulator, RevFirstHalf, SecondHalf)
reverse(Right, [], Left, Left, Right).
reverse([X|Xs], [_,_|Rem], Acc, Left, Right) :-
reverse(Xs, Rem, [X|Acc], Left, Right).
As you can see, the hare is just here to be traversed two-at-a-time until it becomes empty, which triggers our base case. This works fine for even-length lists.
For odd-length lists we must handle them by noticing that the hare is a single-item list. Then we need to produce two more solutions, so we have two clauses for the single-item list case: one that places the next tortoise item into the first half list, one that places it into the second half:
reverse([X|Xs], [_], Acc, Left, Right) :-
reverse([X|Xs], [], Acc, Left, Right).
reverse([X|Xs], [_], Acc, Left, Right) :-
reverse(Xs, [], [X|Acc], Left, Right).
This produces the results you want. It's a little hard to understand what is going on at first, but it is more efficient than the accepted answer, probably because it doesn't allocate as much. For comparison, I ran both on a list of 5 million entries; this version ran somewhat faster:
?- length(L, 5000000), time(splitflip(L, Left, Right)).
% 5,000,014 inferences, 0.806 CPU in 0.806 seconds (100% CPU, 6200835 Lips)
L = [_58, _64, _70, _76, _82, _88, _94, _100, _106|...],
Left = [_15000052, _15000046, _15000040, _15000034, _15000028, _15000022, _15000016, _15000010, _15000004|...],
Right = [_15000058, _15000064, _15000070, _15000076, _15000082, _15000088, _15000094, _15000100, _15000106|...].
?- length(L, 5000000), time(divide(L, Left, Right)).
% 2,500,001 inferences, 0.567 CPU in 0.568 seconds (100% CPU, 4405613 Lips)
L = [_850, _856, _862, _868, _874, _880, _886, _892, _898|...],
Left = [_15000844, _15000838, _15000832, _15000826, _15000820, _15000814, _15000808, _15000802, _15000796|...],
Right = [_15000850, _15000856, _15000862, _15000868, _15000874, _15000880, _15000886, _15000892, _15000898|...] .
Edit: Original answer follows.
This is only a partial solution. I'm missing something obvious about how to convert this to the full solution, and I don't have a lot of time at the moment to finish it, but I thought it might be useful to share anyway. And probably another prolog answerer will see immediately where the problem is.
My plan here is basically to be reversing the list and tracking the index I'm at from the start on the way into the recursive call, and be tracking the index I'm at from the end on the way out of the recursive call. With that in-hand, if the indexes match, we take the reversed prefix and the suffix and that is the result. We can define "match" as the indexes are within 1 of each other, with a separate predicate.
reverse(_, [], 0, Z, Z).
reverse(I, [X|Xs], J1, Z, Acc) :-
succ(I, I1),
reverse(I1, Xs, J, Z, [X|Acc]),
succ(J, J1).
The combination of pieces that I think we need are visible in this code when viewed in the trace. output:
[trace] ?- reverse(0, [a,b,c,d], _, R, []).
Call: (8) reverse(0, [a, b, c, d], _694, _696, []) ? creep
Call: (9) succ(0, _964) ? creep
Exit: (9) succ(0, 1) ? creep
Call: (9) reverse(1, [b, c, d], _972, _696, [a]) ? creep
Call: (10) succ(1, _970) ? creep
Exit: (10) succ(1, 2) ? creep
Call: (10) reverse(2, [c, d], _978, _696, [b, a]) ? creep
Call: (11) succ(2, _976) ? creep
Exit: (11) succ(2, 3) ? creep
Call: (11) reverse(3, [d], _984, _696, [c, b, a]) ? creep
Call: (12) succ(3, _982) ? creep
Exit: (12) succ(3, 4) ? creep
Call: (12) reverse(4, [], _990, _696, [d, c, b, a]) ? creep
Exit: (12) reverse(4, [], 0, [d, c, b, a], [d, c, b, a]) ? creep
Call: (12) succ(0, _988) ? creep
Exit: (12) succ(0, 1) ? creep
Exit: (11) reverse(3, [d], 1, [d, c, b, a], [c, b, a]) ? creep
Call: (11) succ(1, _988) ? creep
Exit: (11) succ(1, 2) ? creep
Exit: (10) reverse(2, [c, d], 2, [d, c, b, a], [b, a]) ? creep
Call: (10) succ(2, _988) ? creep
Exit: (10) succ(2, 3) ? creep
Exit: (9) reverse(1, [b, c, d], 3, [d, c, b, a], [a]) ? creep
Call: (9) succ(3, _694) ? creep
Exit: (9) succ(3, 4) ? creep
Exit: (8) reverse(0, [a, b, c, d], 4, [d, c, b, a], []) ? creep
R = [d, c, b, a] .
Note particularly this line:
Exit: (10) reverse(2, [c, d], 2, [d, c, b, a], [b, a]) ? creep
As you can see, at this moment we have I = J = 2, and we have a list [b,a] and a list [c,d]. This gives me hope that this is close, but for whatever reason I'm not seeing the bridge to the complete solution. Maybe you will!

Prolog: lists consisting of one element

I believe that the problem I have is very silly, but couldn't find an answer searching online. I want a function to always return a list of elements, even in the case the list consists of just one element. I don't know if it is always like that in prolog (i.e. that one element lists are transformed in a simple constant) but here I go into detail.
First off, I have a set of predicates (like composeBase(<,<,[<]).) based on which the function I was talking about, compose/3, makes its computations:
compose(X, Y, Z):-
compositionBase(X,Y,Z).
So, for instance, compose/3 does this:
compose(<, <, L).
L = (<) .
compose(<, =, L).
L = (<) .
compose(=, =, L).
L = (=) .
So, why in the first example does it not return [<] if the predicate says composeBase(<,<,[<]).? Is there a way to make it return the list [<] instead?
If there is not or my question doesn't make any sense, here is where the problem comes with not being able to do so (if there is, don't even bother to go on reading of course! :) Unless you feel like giving me suggestions for my naive code, which I would very much appreciate as a newbie).
I have to write a function composeList/3 which, given two lists of elements, should compute a list containing all the possible compositions of the elements in the two set.
Therefore, for example, composeList/3 should do this:
composeList([<], [<, =], L).
L = [<, =] .
composeList([<,=], [<, =], L).
L = [<, =] .
And here's the code:
composeList(_,[],[]).
composeList([],_,[]).
composeList([X| Xs], [Y| Ys], L):-
compose(X,Y,L1),
composeList(Xs, [Y| Ys], L2),
composeList([X| Xs], Ys, L3),
union(L1, L2, L4),
union(L3,L4,L).
So, as confirmed by doing trace., I believe the problem occurs when compose/3 returns a simple constant and not a list. For instance, in the example above it returns false . because, since L1 is set <, the first union fails. How can I fix this in a casual way?
By the way, here is what trace. does:
[trace] 123 ?- compositionListe([<],[<,=],L).
Call: (7) compositionListe([<], [<, =], _G8032) ? creep
Call: (8) compose(<, <, _G8122) ? creep
Call: (9) compositionBase(<, <, _G8122) ? creep
Exit: (9) compositionBase(<, <, [<]) ? creep
Exit: (8) compose(<, <, [<]) ? creep
Call: (8) compositionListe([], [<, =], _G8128) ? creep
Exit: (8) compositionListe([], [<, =], []) ? creep
Call: (8) compositionListe([<], [=], _G8131) ? creep
Call: (9) compose(<, =, _G8131) ? creep
Exit: (9) compose(<, =, <) ? creep
Call: (9) compositionListe([], [=], _G8134) ? creep
Exit: (9) compositionListe([], [=], []) ? creep
Call: (9) compositionListe([<], [], _G8137) ? creep
Exit: (9) compositionListe([<], [], []) ? creep
Call: (9) lists:union(<, [], _G8137) ? creep
Fail: (9) lists:union(<, [], _G8137) ? creep
Thank you very much in advance!

Prolog recursion not working as intended

I have to write a function that filters out palindromes from a given list (palindrome are words which are the same in reverse like abba)
palindroom([], []).
palindroom([X|Xs], Y):-
( atom_chars(X, Z),
reverse(Z, K),
atom_chars(D,K),
atom_chars(P,Z),
D==P,
palindroom(Xs,[P|Y])
;
palindroom(Xs,Y)
).
I make the list elem into char array then reverse it and make it back into a string then compare the two, if it is I add it Y.
Here is my stacktrace where everything is going correctly until:
[trace] 44 ?- palindroom(["abba"], X).
Call: (7) palindroom(["abba"], _G5269) ? creep
Call: (8) atom_chars("abba", _G5351) ? creep
Exit: (8) atom_chars("abba", [a, b, b, a]) ? creep
Call: (8) lists:reverse([a, b, b, a], _G5363) ? creep
Exit: (8) lists:reverse([a, b, b, a], [a, b, b, a]) ? creep
Call: (8) atom_chars(_G5386, [a, b, b, a]) ? creep
Exit: (8) atom_chars(abba, [a, b, b, a]) ? creep
Call: (8) atom_chars(_G5386, [a, b, b, a]) ? creep
Exit: (8) atom_chars(abba, [a, b, b, a]) ? creep
Call: (8) abba==abba ? creep
Exit: (8) abba==abba ? creep
Call: (8) palindroom([], [abba|_G5269]) ? creep
Fail: (8) palindroom([], [abba|_G5269]) ? creep
Redo: (7) palindroom(["abba"], _G5269) ? creep what is happening here? and why?
Call: (8) palindroom([], _G5269) ? creep
Exit: (8) palindroom([], []) ? creep
Exit: (7) palindroom(["abba"], []) ? creep
X = [].
I have another program with the same problem, could anyone help me? is the base of the recursion wrong or smt?
EDIT!!
got it working with
palindrome(Xs) :-
reverse(Xs, Xs).
cycle([],[]).
cycle([X|Xs], Y):-
atom_chars(X,Z),
palindrome(Z),
Y = [X|K],
cycle(Xs,K);
cycle(Xs,Y).
I misunderstood recursion in Prolog afterall. thanks #repeat and #lurker
How about defining palindrome/1 like this, using the widely available list predicate reverse/2?
palindrome(Xs) :-
reverse(Xs, Xs).
Sample queries:
:- palindrome([a,b,b,a]).
true.
:- palindrome([a,b,X,Y]).
X = b, Y = a.
Last, let's not forget about the most general query!
?- palindrome(Xs).
Xs = []
; Xs = [_A]
; Xs = [_A,_A]
; Xs = [_A,_B,_A]
; Xs = [_A,_B,_B,_A]
; Xs = [_A,_B,_C,_B,_A]
...

My Prolog "translator" script returns an empty list

I am trying to write a simple translator, where you enter a list of numerical numbers and it returns their string values like so :
translate([1,2,3], X).
X=[one, two, three].
The code I have written works...except it returns an empty list. Here is my code and the trace:
means(1, one).
means(2, two).
means(3, three).
means(4, four).
means(5, five).
means(6, six).
means(7, seven).
means(8, eight).
means(9, nine).
means(10, ten).
translate([], X).
translate([H|T], []):-
means(H, X),
translate(T, X).
translate([H|T], X):-
means(H, Y),
translate(T, [X|Y]).
[trace] 1 ?- translate([1,2,3], X).
Call: (6) translate([1, 2, 3], _G2219) ? creep
Call: (7) means(1, _G2301) ? creep
Exit: (7) means(1, one) ? creep
Call: (7) translate([2, 3], one) ? creep
Call: (8) means(2, _G2301) ? creep
Exit: (8) means(2, two) ? creep
Call: (8) translate([3], [one|two]) ? creep
Call: (9) means(3, _G2304) ? creep
Exit: (9) means(3, three) ? creep
Call: (9) translate([], [[one|two]|three]) ? creep
Exit: (9) translate([], [[one|two]|three]) ? creep
Exit: (8) translate([3], [one|two]) ? creep
Exit: (7) translate([2, 3], one) ? creep
Exit: (6) translate([1, 2, 3], []) ? creep
X = [] .
My other question is: why is my list concatenating as [[one|two]|three]? instead of [one, two, three]?
Thanks,
You get empty list because of translate([H|T], []) clause.
The whole program (except means facts) can be just this simple:
translate([], []).
translate([NumH | NumT], [WordH | WordT]) :-
means(NumH, WordH),
translate(NumT, WordT).
Doing it manually Sergey's way is good practice, but you can simplify even further with maplist/3:
translate(Numbers, Words) :- maplist(means, Numbers, Words).

How Does Prolog print out 2 lists as one list, without any append code?

I have the following code, which is apparently the standard way to show the union between 2 lists:
union([Head|Tail],List2,Result) :-
member(Head,List2), union(Tail,List2,Result).
union([Head|Tail],List2,[Head|Result]) :-
\+ member(Head,List2), union(Tail,List2,Result).
union([],List2,List2).
and on the following input:
union([a,b,c,d,2,3], [b,c,3,99], Result).
will give me the following output:
Result = [a,d,2,b,c,3,99] ?
yes
My question is, How does prolog do this? List2 is never changed throught the recursive calls, but at the end, it prints out all elements that make the union between the 2 original lists.
Please help me understand this code.
Thank you.
let's assume that you ask union([1,2],[2],R).
according to the first rule, union([1|[2]],[2],R) would be true if
member(1,[2]) --> false
then prolog will check the second rule union([1|[2]],[2],[1|R]) will be true if
+member(1,[2]) --> true
and union([2],[2],R)
now, union([2|[]],[2],R) would be true (1st rule) if
member(2,[2]) -->true
and union([],[2],R)
union([],[2],R) would be true (3rd rule) if R=[2]
so R=[2] and therefore the first call to union returns [1|[2]] = [1,2]
a useful tool to find out "how prolog does it" is trace/0:
2 ?- trace.
true.
[trace] 2 ?- union([1,2],[2],R).
Call: (6) union([1, 2], [2], _G543) ? creep
Call: (7) lists:member(1, [2]) ? creep
Fail: (7) lists:member(1, [2]) ? creep
Redo: (6) union([1, 2], [2], _G543) ? creep
Call: (7) lists:member(1, [2]) ? creep
Fail: (7) lists:member(1, [2]) ? creep
Call: (7) union([2], [2], _G619) ? creep
Call: (8) lists:member(2, [2]) ? creep
Exit: (8) lists:member(2, [2]) ? creep
Call: (8) union([], [2], _G619) ? creep
Exit: (8) union([], [2], [2]) ? creep
Exit: (7) union([2], [2], [2]) ? creep
Exit: (6) union([1, 2], [2], [1, 2]) ? creep
R = [1, 2] .
all in all: List2 doesnt not change but the predicate does not return List2 either; it returns a list created by List2 and the unique elements of List1
The algorithm in work here is the following :
1) initialize the result to List2. This part is implemented thanks to :
union([], List2, List2).
2) go through List1 and do the following for each item :
2a) add it to Result if the item is not in List2. That part is implemented thanks to this clause :
union([Head|Tail], List2, [Head|Result]) :-
\+ member(Head, List2),
union(Tail, List2, Result).
2b) don't do anything if the item is in List2. That part is implemented thanks to this clause :
union([Head|Tail], List2, Result) :-
member(Head, List2),
union(Tail, List2, Result).
For step by step comprehension of the prolog execution, please refer to #thanosQR answer.
By the way, note that this predicate needs sets to return a good union, else, a duplicate in List1 will stay a duplicate in Result (so will a duplicate in List2).
That code it's actually rather inefficient, so we can't assume it as the 'standard way' to compute union. At first glance, there are 2 simple optimizations: avoid repeat the membership test, and use memberchk/2 instead of member/2. So we can rewrite it in the following way:
union([Head|Tail], List2, ResultT) :-
( memberchk(Head, List2)
-> ResultT = Result
; ResultT = [Head|Result] ),
union(Tail, List2, Result).
union([],List2,List2).
The difference in performance is huge. With relatively small lists:
...
numlist(1, 10000, A),
numlist(5000, 10000, B),
union(A, B, C),
...
we pass from 62,532,499 inferences to 20,002 inferences, and the test doesn't force the evaluation of all alternatives (backtrack points from member): adding this we need 25,015,004 more inferences, albeit no more solution is available. Here the code from SWI-Prolog lists library:
%% union(+Set1, +Set2, -Set3) is det.
%
% True if Set3 unifies with the union of Set1 and Set2.
% The complexity of this predicate is |Set1|*|Set2|
%
% #see ord_union/3.
union([], L, L) :- !.
union([H|T], L, R) :-
memberchk(H, L), !,
union(T, L, R).
union([H|T], L, [H|R]) :-
union(T, L, R).
It's similar to your version, please note the cut (!)