Reputation: 3546
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.
Upvotes: 2
Views: 787
Reputation: 60034
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 (!)
Upvotes: 1
Reputation: 7493
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
).
Upvotes: 1
Reputation: 5858
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
Upvotes: 1