In SWI-Prolog I want to establish the list L from two lists L1 and L2 with the smallest count of elements under the condition, that 1 ∈ L1
and 1 ∈ L2
.
If 1 ∉ L1
and 1 ∈ L2
, then L = L1
. If 1 ∈ L1
and 1 ∉ L2
, then L = L2
. If 1 ∉ L1
and 1 ∉ L2
, then the predicate returns false.
I could evaluate this in Prolog with the following conditions:
minset_one(D1, D2, T) :- ((member(1, D1), not(member(1, D2))) -> T=D1).
minset_one(D1, D2, T) :- ((not(member(1, D1)), member(1, D2)) -> T=D2).
minset_one(D1, D2, T) :- (member(1, D1), member(1, D2), length(D1,L1), length(D2,L2), L1 >= L2) -> T=D2.
minset_one(D1, D2, T) :- (member(1, D1), member(1, D2), length(D1,L1), length(D2,L2), L2 > L1) -> T=D1.
My problem with that function is, the member function is called very often. Is their a way to reduce the complexity of that predicate in that way, the functions
member(1, D1)
member(1, D2)
length(D1, L1)
length(D2, L2)
are called only one time?
Both your code and the code in the answer of @TessellatingHacker lose logical-purity when the arguments of minset_one/3
are not sufficiently instantiated:
?- D1 = [X,Y,Z], D2 = [U,V], minset_one(D1,D2,T). D1 = [1,Y,Z], D2 = [1,V], T = D2 ; false. % no more solutions!
This is clearly incomplete. There are other solutions. We lost logical-purity.
So, what can we do about this? Basically, we have two options:
D1
, D2
and T
upfront and throw an instantiation_error
when the instantiation is not sufficient.In this answer I want to show how to realise option number two.
The code is based on if_/3
which is the core of library(reif)
.
In short, we reify the truth values of relations and use Prolog indexing on these values.
Using SWI-Prolog 8.4.2:
?- use_module(library(reif)).
First, shorter_than_t(Xs,Ys,T)
reifies "list Xs
is shorter than Ys
" into T
:
shorter_than_t([],Ys,T) :- aux_nil_shorter_than(Ys,T). shorter_than_t([_|Xs],Ys,T) :- aux_cons_shorter_than_t(Ys,Xs,T). aux_nil_shorter_than_t([],false). aux_nil_shorter_than_t([_|_],true). aux_cons_shorter_than_t([],_,false). aux_cons_shorter_than_t([_|Ys],Xs,T) :- shorter_than_t(Xs,Ys,T).
Based on shorter_than_t/3
we define minset_one/3
:
minset_one(D1,D2,T) :- if_(shorter_than_t(D1,D2), if_(memberd_t(1,D1), D1=T, (memberd_t(1,D2,true),D2=T)), if_(memberd_t(1,D2), D2=T, (memberd_t(1,D1,true),D1=T))).
Now let's run above query again:
?- D1 = [X,Y,Z], D2 = [U,V], minset_one(D1,D2,T). D1 = [X,Y,Z], D2 = [1,V], T = D2 ; D1 = [X,Y,Z], D2 = [U,1], T = D2, dif(U,1) ; D1 = [1,Y,Z], D2 = [U,V], T = D1, dif(U,1), dif(V,1) ; D1 = [X,1,Z], D2 = [U,V], T = D1, dif(U,1), dif(V,1), dif(X,1) ; D1 = [X,Y,1], D2 = [U,V], T = D1, dif(U,1), dif(V,1), dif(X,1), dif(Y,1) ; false.
At last, minset_one/3
has become complete!