:- 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"). */