Prolog: lists consisting of one element - list

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!

Related

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

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.

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]
...

PROLOG: Keep list in recursion

So, I've spent a lot of my time trying to figure this out without almost no progress. Hope you could help me.
The goal is, to take a list like this(lets call it baselist): [[[feesc,11],[podshare,11]],[[feesc,11]],[]]. And make it become this: [[feesc,22],[podshare,11]].
I have a predicate responsible to add or sum to the resulting list. Here is the code:
place_key([Key,Value], [], [Key,Value]).
place_key([Key,Value], [[Key,V]|Rest], [[Key,V1]|Rest]) :- V1 is V+Value.
place_key([Key,Value], [[K,V]|Rest], [[K,V]|List2]) :- Key \= K, place_key([Key,Value], Rest, List2)."
If I manually call this method, for simulating the recursion, it works exactly how I want.
Example:
place_key([feesc,11], [], R), place_key([feesc,11],R,J).
So J is = [[feesc,22]].
Expected result is correct.
The problem is to that with recursion.
So basically what I need to do is: iterate through the baselist, when reaching each key/par list, call place_key and keep it in the stack so the recursion keeps it until the last.
Just to point out, I don't want to append, I just need the latest result from place_key.
What I have done so far:
fe([HO|T],NL,R) :- write(HO), place_key(HO,NL,RESULT), fe(T,RESULT,R).
fe(S,R):- fe(S,[],R).
fe([],[]).
feg([HO,T],R) :- fe(HO,RESULT), feg(T,RESULT), R = RESULT.
feg([],[]).
When I run:
[trace] 57 ?- feg([[[feesc,11]],[[feesc,11]]],R).
Call: (6) feg([[[feesc, 11]], [[feesc, 11]]], _G21384) ? creep
Call: (7) fe([[feesc, 11]], _G21484) ? creep
Call: (8) fe([[feesc, 11]], [], _G21485) ? creep
Call: (9) place_key([feesc, 11], [], _G21485) ? creep
Exit: (9) place_key([feesc, 11], [], [[feesc, 11]]) ? creep //Until here, I think this is correct.
Call: (9) fe([], [[feesc, 11]], _G21494) ? creep
Fail: (9) fe([], [[feesc, 11]], _G21494) ? creep
Redo: (9) place_key([feesc, 11], [], _G21485) ? creep
Fail: (9) place_key([feesc, 11], [], _G21485) ? creep
Fail: (8) fe([[feesc, 11]], [], _G21485) ? creep
Fail: (7) fe([[feesc, 11]], _G21484) ? creep
Fail: (6) feg([[[feesc, 11]], [[feesc, 11]]], _G21384) ? creep
false.
What am I doing wrong?
The problem in your case is that you don't define a base-case for fe/3. As you can see, except for your place_key predicate, you also have the following:
fe([HO|T],NL,R) :- write(HO), place_key(HO,NL,RESULT), fe(T,RESULT,R).
fe(S,R):- fe(S,[],R).
fe([],[]).
feg([HO,T],R) :- fe(HO,RESULT), feg(T,RESULT), R = RESULT.
feg([],[]).
I'll try to make this a little more readable, so you can see what's going on:
% fe/3 cases
fe([Head|Tail],Current,Result) :- write(Head), place_key(Head,Current,TempResult), fe(Tail,TempResult,Result).
% fe/2 cases
fe(S,R):- fe(S,[],R).
fe([],[]).
%% this case above is never used, as the first case always matches
% recursive and base cases for feg
feg([HO,T],R) :- fe(HO,RESULT), feg(T,RESULT), R = RESULT.
feg([],[]).
You should rewrite this as following:
fe([],Result,Result).
This is your base case, if the list is empty, the result in-between is equal to the final result. Prolog always tries the first possible match first, so always set your base-case on top.
fe([Head|Tail],Current,Result) :- write(Head), place_key(Head,Current,TempResult), fe(Tail,TempResult,Result).
This is your recursive case, as you had before, which we will put below our base case.
We can drop the second fe/2 case, as the first case always matches and we rewrite to fe/3 anyway, which can handle all cases.
fe(S,R):- fe(S,[],R).
Below are your existing feg/2 cases. Here is also an error, because after your first fe/2-predicate, the RESULT-variable has a value, but it still needs to be able to unify with the call feq(T,RESULT), which will produce a different value. I'll leave this as an exercise.
feg([HO,T],R) :- fe(HO,RESULT), feg(T,RESULT), R = RESULT.
feg([],[]).
Keep your key/pairs as a tuple, a simple term with an arity of 2. Something like K-V or K:V or even tuple(K,V) is preferable to [K,V]. There's a simple reason for this:
K-V, K:V and tuple(K,V) all map to simple structures: -(K,V), :(K,V) and tuple(K,V) respectively, while...
[K,V] is syntactic sugar for a rather more complicated structure .(K,.(V,[])).
Further, you might realize that your key/value pairs are hard to distinguish from the nested list-of-lists. Keeping the Key/Value pairs as tuples makes that distincation clear.
So, let us assume your key/value pairs are represented as K:V. It sounds to me that what you're essentially wanting to do is walk your nested list-of-lists (essentially, a tree), enumerate the key/value pairs it contains and produce the set (unique). Here's one way to do that.
First, a simple predicate to identify a non-empty list:
nonnempty_list( L ) :- nonvar(L) , L = [_|_] .
Then, a simple predicate to walk the nested list of lists and enumerate each key/value pair it finds:
visit( [ K:R | Ls ] , K:R ) . % succeed if the head of the list is a key/value pair.
visit( [ L | Ls ] , KVP ) :- % otherwise...
nonempty_list(L) , % - if the head is a non-empty list ,
visit( L , KVP ) % - visit it.
. %
visit( [_|Ls] , KVP ) :- % finally...
visit( Ls , KVP ) % - recurse down on the tail.
. %
Then you can use the magic of setof/3 to get what you want:
flattened_set( LoL , KVPs ) :-
setof( KVP , visit(LoL,KVP) , KVPs )
.

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 (!)