While this previous answer preserves logical-purity, it shows some inefficiency in queries like:
?- list_contains_at1s([43,42,43,42,42,43], 43, Ps).
Ps = [1,3,6] ; % <------ SWI toplevel indicates lingering choicepoint
false.
In above query the lingering choicepoint is guaranteed to be useless: we know that above use case can never yield more than one solution.
Method 1: explicit indexing and extra helper predicate
The earlier definition of list_contains_at_index1/4
has two recursive clauses—one covering the "equal" case, the other one covering the "not equal" case.
Note that these two recursive clauses of list_contains_at_index1/4
are mutually exclusive, because (=)/2
and dif/2
are mutually exclusive.
How can we exploit this?
By utilizing first-argument indexing together with the reified term equality predicate (=)/3
!
:- use_module(library(reif)).
list_contains_at_index1([], _, [], _).
list_contains_at_index1([E|Es], X, Is0, I1) :-
=(E, X, T), % (=)/3
equal_here_at0_at(T, I1, Is0, Is),
I2 #= I1+1,
list_contains_at_index1(Es, X, Is, I2).
equal_here_at0_at(true , I1, [I1|Is], Is). % index on the truth value ...
equal_here_at0_at(false, _, Is , Is). % ... of reified term equality
Method 2: implicit indexing, no extra helper predicate, using if_/3
For more concise code we can put if_/3
to good use:
list_contains_at_index1([], _, [], _).
list_contains_at_index1([E|Es], X, Is0, I1) :-
if_(E = X, Is0 = [I1|Is], Is0 = Is),
I2 #= I1+1,
list_contains_at_index1(Es, X, Is, I2).
If we re-run above query with new improved code ...
?- list_contains_at1s([43,42,43,42,42,43], 43, Positions).
Positions = [1, 3, 6].
... we see that the query now succeeds deterministically. Mission accomplished!