231 lines
6.4 KiB
Mathematica
231 lines
6.4 KiB
Mathematica
|
:- module day12.
|
||
|
:- interface.
|
||
|
:- import_module basics.
|
||
|
|
||
|
:- pred run(part::in, lines::in, answer::out) is cc_multi.
|
||
|
|
||
|
:- implementation.
|
||
|
:- import_module int.
|
||
|
:- import_module char.
|
||
|
:- import_module string.
|
||
|
:- import_module list.
|
||
|
:- import_module array2d.
|
||
|
|
||
|
:- type point == {int, int}.
|
||
|
|
||
|
:- type grid ---> g(array :: array2d(char), start :: point, end :: point).
|
||
|
|
||
|
:- pred grid(lines::in, grid::out) is semidet.
|
||
|
grid(Lines, g(Arr, {SX, SY}, {EX, EY})) :-
|
||
|
Arr = from_lists(map(to_char_list, Lines)),
|
||
|
find(Arr, 'S', SX, SY),
|
||
|
find(Arr, 'E', EX, EY).
|
||
|
|
||
|
:- pred find_from(array2d(T)::in, T::in, int::in, int::in, int::out, int::out)
|
||
|
is semidet.
|
||
|
find_from(Arr, A, X1, Y1, X2, Y2) :-
|
||
|
bounds(Arr, W, H),
|
||
|
Y1 < H,
|
||
|
(if X1 < W then
|
||
|
B = Arr^elem(X1, Y1),
|
||
|
(if A = B then X2 = X1, Y2 = Y1
|
||
|
else find_from(Arr, A, X1+1, Y1, X2, Y2))
|
||
|
else
|
||
|
find_from(Arr, A, 0, Y1+1, X2, Y2)).
|
||
|
|
||
|
:- pred find(array2d(T)::in, T::in, int::out, int::out) is semidet.
|
||
|
find(Arr, A, X, Y) :- find_from(Arr, A, 0, 0, X, Y).
|
||
|
|
||
|
:- func get(grid, point) = char.
|
||
|
get(G, {X, Y}) = G^array^elem(X, Y).
|
||
|
|
||
|
:- func height(char) = int.
|
||
|
height(C) = I :-
|
||
|
if C = 'S' then I = 0
|
||
|
else if C = 'E' then I = 25
|
||
|
else I = to_int(C) - to_int('a').
|
||
|
|
||
|
:- func height(grid, point) = int.
|
||
|
height(G, P) = height(get(G, P)).
|
||
|
|
||
|
:- pred adj_point(point::in, point::out) is multi.
|
||
|
adj_point({X, Y}, {X+1, Y}).
|
||
|
adj_point({X, Y}, {X-1, Y}).
|
||
|
adj_point({X, Y}, {X, Y+1}).
|
||
|
adj_point({X, Y}, {X, Y-1}).
|
||
|
|
||
|
:- pred adj(grid::in, point::in, point::out) is nondet.
|
||
|
adj(G, P, Q) :-
|
||
|
adj_point(P, Q), in_bounds(G, Q),
|
||
|
abs(height(G, P) - height(G, Q)) =< 1.
|
||
|
|
||
|
:- pred in_bounds(grid::in, point::in) is semidet.
|
||
|
in_bounds(G, {X, Y}) :-
|
||
|
bounds(G^array, W, H),
|
||
|
0 =< X, X < W,
|
||
|
0 =< Y, Y < H.
|
||
|
|
||
|
:- pred index(grid::in, point::out) is nondet.
|
||
|
index(G, {I, J}) :-
|
||
|
bounds(G^array, W, H),
|
||
|
nondet_int_in_range(0, W-1, I),
|
||
|
nondet_int_in_range(0, H-1, J).
|
||
|
|
||
|
|
||
|
:- type path == list(point).
|
||
|
|
||
|
:- type distance ---> i(int); inf.
|
||
|
|
||
|
:- import_module solutions.
|
||
|
|
||
|
:- import_module map.
|
||
|
:- type pm == map(point, point).
|
||
|
|
||
|
:- type nq ---> nq(pq, dm).
|
||
|
:- type dm == map(point, distance).
|
||
|
:- type pq == list({point, distance}).
|
||
|
|
||
|
:- pred remove_min(point::out, distance::out, pq::in, pq::out) is semidet.
|
||
|
remove_min(X, D, [{X0, D0} | PQ0], PQ) :-
|
||
|
if
|
||
|
remove_min(X1, D1, PQ0, PQ1),
|
||
|
D1 @< D0
|
||
|
then
|
||
|
X = X1, D = D1,
|
||
|
PQ = [{X0, D0} | PQ1]
|
||
|
else
|
||
|
X = X0, D = D0,
|
||
|
PQ = PQ0.
|
||
|
|
||
|
:- pred seen(nq::in, point::in) is semidet.
|
||
|
seen(nq(_, S), X) :- contains(S, X).
|
||
|
|
||
|
:- pred next(point::out, distance::out, nq::in, nq::out) is semidet.
|
||
|
next(X, D, nq(!.Q, !.S), nq(!:Q, !:S)) :-
|
||
|
remove_min(X, D, !Q), set(X, D, !S).
|
||
|
|
||
|
:- pred set_distance_pq(point::in, distance::in, pq::in, pq::out) is det.
|
||
|
set_distance_pq(_, _, [], []).
|
||
|
set_distance_pq(X, D, [{Y, E} | Xs], Zs) :-
|
||
|
if X = Y then
|
||
|
Zs = [{X, D} | Xs]
|
||
|
else
|
||
|
set_distance_pq(X, D, Xs, Xs2),
|
||
|
Zs = [{Y, E} | Xs2].
|
||
|
|
||
|
:- pred set_distance(point::in, distance::in, nq::in, nq::out) is det.
|
||
|
set_distance(X, D, nq(!.Q, !.S), nq(!:Q, !:S)) :-
|
||
|
set_distance_pq(X, D, !Q),
|
||
|
(if contains(!.S, X) then det_update(X, D, !S) else true).
|
||
|
|
||
|
:- func get_distance(point, pq, dm) = distance.
|
||
|
:- mode get_distance(in, in, in) = out is semidet.
|
||
|
get_distance(X, [], S) = search(S, X).
|
||
|
get_distance(X, [{Y, D} | Xs], S) = D1 :-
|
||
|
if X = Y then D1 = D else D1 = get_distance(X, Xs, S).
|
||
|
|
||
|
:- func get_distance(point, nq) = distance.
|
||
|
:- mode get_distance(in, in) = out is semidet.
|
||
|
get_distance(X, nq(Q, S)) = get_distance(X, Q, S).
|
||
|
|
||
|
|
||
|
:- pred insert_init(point::in, point::in, pq::in, pq::out) is det.
|
||
|
insert_init(From, X, !Queue) :-
|
||
|
(if X = From then D = i(0) else D = inf),
|
||
|
cons({X, D}, !Queue).
|
||
|
|
||
|
:- pred init_queue(grid::in, point::in, nq::out) is det.
|
||
|
init_queue(G, From, nq(Q, init)) :-
|
||
|
aggregate(index(G), insert_init(From), [], Q).
|
||
|
|
||
|
|
||
|
:- pred get_path_rev(pm::in, point::in, point::in, path::out) is det.
|
||
|
get_path_rev(Map, From, To, Path) :-
|
||
|
if From = To then
|
||
|
Path = []
|
||
|
else if search(Map, To, Prev) then
|
||
|
get_path_rev(Map, From, Prev, Tail),
|
||
|
Path = [To | Tail]
|
||
|
else
|
||
|
To = {X, Y},
|
||
|
die("no predecessor for {%d, %d}", [i(X), i(Y)]).
|
||
|
|
||
|
:- pred get_path(pm::in, point::in, point::in, path::out) is det.
|
||
|
get_path(Map, From, To, reverse(Path)) :- get_path_rev(Map, From, To, Path).
|
||
|
|
||
|
:- func incr(distance) = distance.
|
||
|
incr(inf) = inf.
|
||
|
incr(i(N)) = i(N + 1).
|
||
|
|
||
|
:- pred neighbours(grid::in, point::in, nq::in, point::out) is nondet.
|
||
|
neighbours(G, Point, Queue, N) :-
|
||
|
adj(G, Point, N),
|
||
|
not seen(Queue, N).
|
||
|
|
||
|
:- pred path(grid::in, point::in, point::in, path::out,
|
||
|
nq::in, nq::out, pm::in, pm::out) is semidet.
|
||
|
path(G, From, To, Path, !Queue, !Prev) :-
|
||
|
next(Point, Distance, !Queue),
|
||
|
Distance = inf => die("ow"),
|
||
|
Neighs = solutions(neighbours(G, Point, !.Queue)),
|
||
|
foldl2(pred(Neigh::in, % 🐴
|
||
|
!.Queue::in, !:Queue::out,
|
||
|
!.Prev::in, !:Prev::out) is det :- (
|
||
|
Alt = incr(Distance),
|
||
|
(if Alt @< get_distance(Neigh, !.Queue) then
|
||
|
set_distance(Neigh, Alt, !Queue),
|
||
|
set(Neigh, Point, !Prev)
|
||
|
else true)),
|
||
|
Neighs, !Queue, !Prev),
|
||
|
(if Point = To then
|
||
|
get_path(!.Prev, From, To, Path)
|
||
|
else
|
||
|
path(G, From, To, Path, !Queue, !Prev)).
|
||
|
|
||
|
:- pred path(grid::in, point::in, point::in, path::out) is semidet.
|
||
|
path(G, From, To, Path) :-
|
||
|
init_queue(G, From, Queue),
|
||
|
path(G, From, To, Path, Queue, _, init, _).
|
||
|
|
||
|
:- pred path(grid::in, path::out) is semidet.
|
||
|
path(G, Path) :- path(G, G^start, G^end, Path).
|
||
|
|
||
|
|
||
|
run(_, Lines, Out) :-
|
||
|
if
|
||
|
grid(Lines, G),
|
||
|
init_queue(G, G^start, Q),
|
||
|
path(G, G^start, G^end, Path, Q, nq(Q1, S1), init, Prev)
|
||
|
then
|
||
|
Out = 'new other'({"RESULT", length(Path):int,
|
||
|
"G", G, "Path", Path,
|
||
|
"Q1", Q1,
|
||
|
"S1", to_assoc_list(S1):list(_),
|
||
|
"Prev", to_assoc_list(Prev):list(_)})
|
||
|
else
|
||
|
die("bad input").
|
||
|
|
||
|
/* omg could u imagine if this had worked tho
|
||
|
|
||
|
:- pragma memo(path/5, [fast_loose]).
|
||
|
:- pred path(grid::in, point::in, point::in, path::in, int::out)
|
||
|
is nondet.
|
||
|
path(G, P, P, _, 0).
|
||
|
path(G, P, Q, Seen, Len + 1) :-
|
||
|
adj(G, P, P1),
|
||
|
not member(P1, Seen),
|
||
|
path(G, P1, Q, [P|Seen], Len).
|
||
|
|
||
|
:- pred path(grid::in, point::in, point::in, int::out) is nondet.
|
||
|
path(G, P, Q, Len) :- path(G, P, Q, [], Len).
|
||
|
|
||
|
:- import_module solutions.
|
||
|
|
||
|
run(_, Lines, Out) :-
|
||
|
if grid(Lines, G) then
|
||
|
solutions(path(G, G^start, G^end), Paths),
|
||
|
Out = int(det_head(Paths))
|
||
|
else
|
||
|
die("bad input").
|
||
|
*/
|