Advent of Code 2023 - Day 22
Mix.install([
{:req, "~> 0.4.0"},
{:libgraph, "~> 0.16.0"}
])
Input
opts = [headers: [{"cookie", "session=#{System.fetch_env!("LB_AOC_SESSION")}"}]]
puzzle_input = Req.get!("https://adventofcode.com/2023/day/22/input", opts).body
input =
for row <- String.split(puzzle_input, "\n", trim: true) do
String.split(row, [",", "~"])
|> Enum.map(&String.to_integer/1)
end
|> Enum.sort_by(fn [_, _, z1, _, _, z2] -> min(z1, z2) end)
|> Enum.with_index()
defmodule SandSlabs do
def fall(falling, fallen, downs \\ %{}) do
case tick(falling, fallen, downs) do
{[], all_fallen, downs} -> {Enum.reverse(all_fallen), downs}
{still_falling, already_fallen, downs} -> fall(still_falling, already_fallen, downs)
end
end
defp tick([first | rest], fallen, downs) do
if is_fallen?(first, fallen) do
{rest, [first | fallen], downs}
else
{[down(first) | rest], fallen, inc(downs, first)}
end
end
defp inc(downs, {_, i}) do
Map.update(downs, i, 1, &(&1 + 1))
end
defp down({[x1, y1, z1, x2, y2, z2], i}) do
{[x1, y1, z1 - 1, x2, y2, z2 - 1], i}
end
defp is_fallen?({[_, _, z1, _, _, z2], _i}, _fallen) when z1 == 0 or z2 == 0 do
true
end
defp is_fallen?(a, fallen) do
Enum.any?(fallen, fn b -> touches?(a, b) end)
end
def touches?({[xa1, ya1, za1, xa2, ya2, za2], _}, {[xb1, yb1, zb1, xb2, yb2, zb2], _i}) do
(za1 == zb1 + 1 or za2 == zb2 + 1 or za1 == zb2 + 1 or za2 == zb1 + 1) and
(xa1 in xb1..xb2 or xa2 in xb1..xb2 or xb1 in xa1..xa2 or xb2 in xa1..xa2) and
(ya1 in yb1..yb2 or ya2 in yb1..yb2 or yb1 in ya1..ya2 or yb2 in ya1..ya2)
end
end
{fallen_bricks, _downs} = SandSlabs.fall(input, [])
Part 1
init_graph =
for {v, i} <- fallen_bricks, reduce: Graph.new() do
g -> Graph.add_vertex(g, {v, i})
end
graph =
for {a, i} <- Graph.vertices(init_graph),
{b, j} <- Graph.vertices(init_graph),
a != b,
SandSlabs.touches?({b, j}, {a, i}),
reduce: init_graph do
g -> Graph.add_edge(g, {a, i}, {b, j})
end
graph
|> Graph.vertices()
|> Stream.filter(fn lower ->
to_upper = Graph.out_edges(graph, lower)
if length(to_upper) == 0 do
true
else
to_upper
|> Stream.map(fn %{v2: v2} -> Graph.in_edges(graph, v2) end)
|> Stream.map(&length/1)
|> Enum.all?(&(&1 > 1))
end
end)
|> Enum.count()
Part 2
0..length(fallen_bricks)
|> Stream.map(fn i ->
fallen_bricks
|> List.pop_at(i)
|> then(fn {_, rest} -> rest end)
|> SandSlabs.fall([])
|> then(fn {_bricks, downs} -> Enum.count(downs) end)
end)
|> Enum.sum()