The main problem is intersection/4
. I assume you wanted to write a deterministic predicate intersection/3
the first two arguments of which are fully instantiated at call time and the last argument of which is an output argument. By deterministic, I mean that intersection/3
should succeed exactly once without leftover choice points. The SWI-Prolog documentation contains a useful overview of determinism and mode declarations (although it does not enforce them).
It is useful to begin by writing a declarative specification of the predicate following the inductive definition of lists:
- The intersection of
[]
and Ys
is []
.
- The intersection of
[A|Xs]
and Ys
is A
prepended to the intersection of Xs
and Ys
if A
is a member of Ys
.
- The intersection of
[A|Xs]
and Ys
is the intersection of Xs
and Ys
if A
is not a member of Ys
.
The simplest translation of this specification into standard Prolog is:
intersection([],_,[]).
intersection([A|Xs],Ys,[A|Zs]) :-
member(A,Ys),
intersection(Xs,Ys,Zs).
intersection([A|Xs],Ys,Zs) :-
\+ member(A,Ys),
intersection(Xs,Zs).
If the first call to member/2
succeeds the second should fail. In order to avoid backtracking, unifying the current goal with the head of the second clause, and performing a redundant call to member/2
, we place a cut after the occurrence of member/2
in the second clause.
intersection([],_,[]).
intersection([A|Xs],Ys,[A|Zs]) :-
member(A,Ys),
!,
intersection(Xs,Ys,Zs).
intersection([_|Xs],Ys,Zs) :-
intersection(Xs,Ys).
If the current goal unifies with the head of the first clause, it will not unify with the heads of later clauses. In order to prevent spurious backtracking, we place a cut in the (empty) body of the first clause. Whether this cut is necessary depends on your choice of Prolog implementation.
intersection([],_,[]) :-
!.
intersection([A|Xs],Ys,[A|Zs]) :-
member(A,Ys),
!,
intersection(Xs,Ys,Zs).
intersection([_|Xs],Ys,Zs) :-
intersection(Xs,Ys).
We are only interested in checking membership in the second list. Thus, we can replace the occurrence of member/2
with the semideterministic predicate memberchk/2
. Here, semideterministic means that memberchk/2
succeeds or fails exactly once without leftover choice points.
intersection([],_,[]).
!.
intersection([A|Xs],Ys,[A|Zs]) :-
memberchk(A,Ys),
!,
intersection(Xs,Ys,Zs).
intersection([_|Xs],Ys,Zs) :-
intersection(Xs,Ys).
This implementation of intersection/3
is nearly identical to the implementation found in the SWI-Prolog library lists.pl
. You can combine this predicate with an implementation of last/2
in order to complete your program. For example:
last_duplicate_tripled(Xs,Ys,N) :-
intersection(Xs,Ys,Zs),
last(Zs,M),
N is M * 3.
From here, I recommend doing the following:
- Implement
intersection/3
using metalogical predicates like findall/3
.
- Read @false's answer to this question.
- Read @repeat's answer to this question.
They are doing something much more interesting and sophisticated than I attempted in this answer.