aoc23/08/part2.idiomaticbutslow.pl
2023-12-09 22:01:20 -08:00

87 lines
2.9 KiB
Prolog

% :- table step_at/2.
% :- table node_at/3.
:- table reachable_endnode/2.
:- op(700, xfx, l).
:- op(700, xfx, r).
From l To :- From to To-_.
From r To :- From to _-To.
answer(Answer) :-
starts(Starts),
routes(Starts, [Route1 | RestOfRoutes]),
foldl(unify2, RestOfRoutes, Route1, _-(_-Answer-_)).
% unify2 combines 2 routes into one with its own stride-offsets-dests
unify2(Route1, Route2, NewRoute) :-
once(findnsols(2, S, converge(Route1, Route2, S), SolutionPair)),
SolutionPair = [NewA-LenA-NewZ, _-LenB-_],
NewStride is LenB - LenA,
NewRoute = NewA-(NewStride-LenA-[0-NewZ]).
% Len = Stride1*X1 + Offset1 + Dest1 = Stride2*X2 + Offset2 + Dest2
% For performance, Route1's Stride should =< Route2's Stride
converge(Route1, Route2, NewA-Len-NewZ) :-
writef('Combining %t - %t\n', [Route1, Route2]),
Route1 = A1-(Stride1-Offset1-Dests1),
Route2 = A2-(Stride2-Offset2-Dests2),
natnum(X2),
pick([Dests1, Dests2], [Dest1-Z1, Dest2-Z2]),
0 is (Stride2*X2 + Offset2 + Dest2 - Offset1 - Dest1) mod Stride1,
Len is Stride2*X2 + Offset2 + Dest2,
atom_concat(A1, A2, NewA), atom_concat(Z1, Z2, NewZ).
routes(Starts, Routes) :-
maplist([S, S-Route]>>(route(S, Route)), Starts, Routes).
% route builds a route (Stride-Offset-Internals) for a particular starting node.
route(Start, Stride-FirstN-[0-FirstDest | Dests]) :-
direction_len(DirLen),
once(reachable_endnode(Start, FirstN-FirstDest)),
findall(ShiftedN-Dest,
( reachable_endnode(Start, N-Dest),
ShiftedN is N - FirstN,
ShiftedN =\= 0,
(0 is ShiftedN mod DirLen -> !; true)),
TmpDests),
last(TmpDests, Stride-LastDest),
append(Dests, [Stride-LastDest], TmpDests).
% Dest is reachable from Start after N steps
reachable_endnode(Start, N-Dest) :-
natnum(N),
node_at(N, Start, Dest),
is_end(Dest).
starts(Starts) :- findall(X, X to _, Nodes), include(is_start, Nodes, Starts).
is_start(Node) :- atom_chars(Node, [_, _, a]).
is_end(Node) :- atom_chars(Node, [_, _, z]).
% node_at(N, Dir, From, Dest) :- Dest is reached after moving N step from From.
node_at(0, Start, Start).
node_at(N, From, Dest) :-
N > 0,
PrevN is N - 1,
step_at(PrevN, PrevStep),
G =.. [PrevStep, PrevNode, Dest], G,
node_at(PrevN, From, PrevNode).
% Step is the N-th step (counting starts from 0).
step_at(N, Step) :-
direction_list(Dir),
length(Dir, DirLen),
divmod(N, DirLen, _, Remainder),
nth0(Remainder, Dir, Step).
direction_list(Dir) :- direction(Str), atom_chars(Str, Dir).
direction_len(Len) :- direction_list(D), length(D, Len).
% pick one item from each sublist of ListOfLists & put them into Items in order.
% [[1,2,3], [4], [5,6]] -> [1,4,5]; [1,4,6]; [2,4,5]; [2,4,6]; [3,4,5]; [3,4,6].
pick(ListOfLists, Items) :-
maplist([SubList, X]>>(member(X, SubList)), ListOfLists, Items).
natnum(0).
natnum(N) :- natnum(N0), N is N0 + 1.