You can also give up most of the between/3. You only
need a point/1 for the last element of your list _L.
Like this, also swapping the two next/2 call argument order:
next(X-Y, X2-Y) :- X < 3, X2 is X+1.
next(X-Y, X2-Y) :- X > 0, X2 is X-1.
next(X-Y, X-Y2) :- Y < 3, Y2 is Y+1.
next(X-Y, X-Y2) :- Y > 0, Y2 is Y-1.
point(X-Y) :-
between(0, 3, X),
between(0, 3, Y).
line([P1, P2]) :-
point(P2),
next(P2, P1).
line([P1 | Rs]) :-
line(Rs),
Rs = [P2 | _],
next(P2, P1),
\+ member(P1, Rs).
Swpping argument order of next/2 preserves validity
since next/2 is symmetric, no re-programming of next/2
was needed. But it helps to establish ground input
to ground output dataflow in each next/2 call:
?- time(aggregate_all(count, (length(_L, 16), line(_L)), C)).
% 893,214 inferences, 0.047 CPU in 0.058 seconds (81% CPU, 19055232 Lips)
C = 552.
The number of inferences went down from 1,400,301
to 893,214
, because next/2 is only called with mode (+,-)
and not anymore with mode (-,-)
.
Edit 06.10.2024
I don’t know how to make next/2 fully declarative.
Maybe something with succ/2. But then there is still
the problem of bounds checking. The above solution
is highly optimized to mode (+,-)
and also some invariants
of the search. So you don’t find between(0,2,X)
in the
first clause of next/2, rather the more efficient X < 3
,
and the test X >= 0
is omitted, since the invariant is that
next/2 never walks out of the square. You could of course
go back to CLP(FD), to implement next/2.