aoc2022/day18.m
2022-12-18 21:40:49 +01:00

130 lines
4.3 KiB
Mathematica

:- module day18.
:- 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 digraph.
:- import_module ranges.
:- import_module solutions.
:- import_module set_tree234.
:- type set(T) == set_tree234(T).
:- type point3 ---> p(x :: int, y :: int, z :: int).
:- type bounds ---> b(lo :: point3, hi :: point3).
:- type dir ---> left; right; up; down; fore; back.
:- type vertex ---> empty(point3) ; full(point3, dir).
:- type points == set(point3).
:- type graph == digraph(vertex).
:- type key == digraph_key(vertex).
:- pred lines(lines::in, list(point3)::out) is semidet.
lines(Lines, Ps) :- map(line, Lines, Ps).
:- pred line(string::in, point3::out) is semidet.
line(Str, P) :- line(P, to_char_list(Str), []).
:- pred line(point3::out, chars::in, chars::out) is semidet.
line(p(X, Y, Z)) --> number(X), [','], number(Y), [','], number(Z).
:- pred number(int::out, chars::in, chars::out) is semidet.
number(N) --> digits(S), {to_int(from_char_list(S), N)}.
:- pred digits(chars::out, chars::in, chars::out) is semidet.
digits([C | Cs]) -->
[C], {is_digit(C)},
(if digits(Cs0) then {Cs = Cs0} else {Cs = []}).
:- func pointwise(func(int) = int, point3) = point3.
pointwise(F, p(X,Y,Z)) = p(F(X), F(Y), F(Z)).
:- pred pointwise(pred(int, int, int), point3, point3, point3).
:- mode pointwise(pred(in, in, out) is det, in, in, out) is det.
pointwise(P, p(X1,Y1,Z1), p(X2,Y2,Z2), p(X,Y,Z)) :-
P(X1, X2, X), P(Y1, Y2, Y), P(Z1, Z2, Z).
:- pred bounds(list(point3), bounds).
:- mode bounds(in, out) is semidet.
:- mode bounds(in(non_empty_list), out) is det.
bounds([P | Ps], b(Lo, Hi)) :-
foldl(pointwise(min), Ps, P, Lo0), foldl(pointwise(max), Ps, P, Hi0),
Lo = pointwise(func(I) = I - 1, Lo0),
Hi = pointwise(func(I) = I + 1, Hi0).
:- pred in_bounds(bounds::in, point3::in) is semidet.
in_bounds(b(p(X1,Y1,Z1), p(X2,Y2,Z2)), p(X,Y,Z)) :-
X1 =< X, X =< X2, Y1 =< Y, Y =< Y2, Z1 =< Z, Z =< Z2.
:- pred nondet_in_bounds(bounds::in, point3::out) is nondet.
nondet_in_bounds(b(p(X1,Y1,Z1), p(X2,Y2,Z2)), p(X,Y,Z)) :-
Xs = range(X1, X2), Ys = range(Y1, Y2), Zs = range(Z1, Z2),
nondet_member(X, Xs), nondet_member(Y, Ys), nondet_member(Z, Zs).
:- pred adj0(point3::in, point3::out, dir::out) is multi.
adj0(p(X,Y,Z), p(X1,Y,Z), D) :- X1 = X - 1, D = left ; X1 = X + 1, D = right.
adj0(p(X,Y,Z), p(X,Y1,Z), D) :- Y1 = Y - 1, D = up ; Y1 = Y + 1, D = down.
adj0(p(X,Y,Z), p(X,Y,Z1), D) :- Z1 = Z - 1, D = fore ; Z1 = Z + 1, D = back.
:- pred adj(bounds::in, point3::in, {point3,dir}::out) is nondet.
adj(Bounds, P, {Q, D}) :- adj0(P, Q, D), in_bounds(Bounds, Q).
:- pred build_graph(list(point3)::in, graph::out, key::out) is cc_nondet.
build_graph(PointList, Graph, StartKey) :-
bounds(PointList, Bounds),
list_to_set(PointList, Points),
build_graph0(Bounds, Points, Graph),
nondet_in_bounds(Bounds, Start),
not member(Start, Points),
search_key(Graph, empty(Start), StartKey).
:- pred build_graph0(bounds::in, points::in, graph::out) is det.
build_graph0(Bounds, Full, Graph) :-
solutions(nondet_in_bounds(Bounds), AllPoints),
foldl(add_adj_vertices(Bounds, Full), AllPoints, init, Graph).
:- pred add_adj_vertices(bounds::in, points::in, point3::in,
graph::in, graph::out) is det.
add_adj_vertices(Bounds, Full, P, !G) :-
if member(P, Full) then true else
solutions(adj(Bounds, P), Adj),
foldl(add_vertex(Full, P), Adj, !G).
:- pred add_vertex(points::in, point3::in, {point3,dir}::in,
graph::in, graph::out) is det.
add_vertex(Full, From, {To, Dir}, !G) :-
ToV = ite((pred) is semidet :- member(To, Full), full(To, Dir), empty(To)),
add_vertices_and_edge(empty(From), ToV, !G).
:- func count_faces(graph, key) = int.
count_faces(Graph, Start) = Count :-
dfs(Graph, Start, SeenKeys),
foldl(pred(K::in, Acc0::in, Acc::out) is det :-
(if lookup_vertex(Graph, K) = full(_, _) then Acc = Acc0 + 1
else Acc = Acc0),
SeenKeys, 0, Count).
run(one, _, _) :- die("bqn").
run(two, Lines, Out) :-
if
lines(Lines, PointList),
build_graph(PointList, Graph, Start)
then
Out = int(count_faces(Graph, Start))
else
die("bad input").