1
0
Fork 0
advent-of-code/2024/lib/2024/12.ex
2024-12-12 16:54:30 -05:00

112 lines
2.6 KiB
Elixir

import AOC
import AOC.Prelude
aoc 2024, 12 do
def p1(input) do
input
|> map_grid()
|> group_plots()
|> Enum.map(&size_plot/1)
|> Enum.map(fn {area, perimeter, _} -> area * perimeter end)
|> Enum.sum()
end
def p2(input) do
input
|> map_grid()
|> group_plots()
|> Enum.map(&size_plot/1)
|> Enum.map(fn {area, _, sides} -> area * sides end)
|> Enum.sum()
end
## group plots
defp group_plots(map, plots \\ [])
defp group_plots(map, plots) when map_size(map) == 0, do: plots
defp group_plots(map, plots) do
{pos, plant} = Enum.at(map, 0)
plot = walk_plot(map, pos, plant)
{_, map} = Map.split(map, MapSet.to_list(plot))
group_plots(map, [plot | plots])
end
defp size_plot(plot) do
area = MapSet.size(plot)
perimeter =
for pos <- plot, reduce: 0 do
sum ->
sum +
([&n/1, &s/1, &e/1, &w/1]
|> Enum.map(& &1.(pos))
|> Enum.count(&(not MapSet.member?(plot, &1))))
end
{area, perimeter, count_sides(plot)}
end
## traversal
defp walk_plot(map, pos, plant, plot \\ MapSet.new()) do
plot = MapSet.put(plot, pos)
[&n/1, &s/1, &e/1, &w/1]
|> Enum.map(& &1.(pos))
|> Enum.reject(&MapSet.member?(plot, &1))
|> Enum.filter(&(Map.get(map, &1) == plant))
|> Enum.reduce(plot, fn pos, plot ->
walk_plot(map, pos, plant, plot)
end)
end
def n({x, y}), do: {x, y + 1}
def s({x, y}), do: {x, y - 1}
def e({x, y}), do: {x + 1, y}
def w({x, y}), do: {x - 1, y}
defp count_sides(plot) do
# find exterior faces
exterior_faces =
for pos <- plot, dir <- ~w[n e s w]a, exterior?(plot, pos, dir) do
{pos, dir}
end
exterior_faces
# group exterior faces
|> Enum.group_by(
fn
{{_, y}, dir} when dir in ~w[n s]a -> {dir, y}
{{x, _}, dir} when dir in ~w[e w]a -> {dir, x}
end,
fn
{{x, _}, dir} when dir in ~w[n s]a -> x
{{_, y}, dir} when dir in ~w[e w]a -> y
end
)
|> Map.values()
# count groups of exterior faces, splitting non-monotonic sections
|> Enum.map(fn group ->
{faces, _} =
group
|> Enum.sort()
|> Enum.reduce({0, nil}, fn
n, {sides, nil} -> {sides + 1, n}
n, {sides, last} when last + 1 == n -> {sides, n}
n, {sides, _last} -> {sides + 1, n}
end)
faces
end)
|> Enum.sum()
end
defp exterior?(plot, pos, dir) do
neighboring_pos = apply(__MODULE__, dir, [pos])
not MapSet.member?(plot, neighboring_pos)
end
end