diff --git a/2024/README.md b/2024/README.md index a2e05b2..dedd619 100644 --- a/2024/README.md +++ b/2024/README.md @@ -3,7 +3,7 @@ | S | M | T | W | T | F | S | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | | [1] | [2] | [3] | [4] | [5] | [6] | [7] | -| [8] | [9] | [10]| [11]| 12 | 13 | 14 | +| [8] | [9] | [10]| [11]| [12]| 13 | 14 | | 15 | 16 | 17 | 18 | 19 | 20 | 21 | | 22 | 23 | 24 | 25 | | | | @@ -18,3 +18,4 @@ [9]: ./lib/2024/9.ex [10]: ./lib/2024/10.ex [11]: ./lib/2024/11.ex +[12]: ./lib/2024/12.ex diff --git a/2024/lib/2024/12.ex b/2024/lib/2024/12.ex new file mode 100644 index 0000000..26167d0 --- /dev/null +++ b/2024/lib/2024/12.ex @@ -0,0 +1,112 @@ +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 diff --git a/README.md b/README.md index c783e5c..7bf0285 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ | [2021] | **43/50** 🌟 | Elixir | | [2022] | **14/50** 🌟 | Elixir, Haskell | | [2023] | **19/50** 🌟 | Elixir, Haskell | -| [2024] | **22/50** 🌟 | Elixir | -| **Total** | **178** 🌟| | +| [2024] | **24/50** 🌟 | Elixir | +| **Total** | **180** 🌟| | [2015]: ./2015 [2017]: ./2017