Advent of Code 2024 - Day 12
Mix.install([
{:kino_aoc, "~> 0.1.7"}
])
Section
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "12", System.fetch_env!("LB_AOC_SESSION"))
grid =
puzzle_input
|> String.split("\n")
|> Enum.map(&String.to_charlist/1)
garden =
for {row, i} <- Enum.with_index(grid),
{plant, j} <- Enum.with_index(row),
into: %{},
do: {{i, j}, plant}
defmodule AoC2024.Day12 do
def regions(garden) do
garden
|> regions([])
|> Enum.map(&MapSet.new/1)
end
def border_tiles(region) do
Enum.filter(region, fn tile ->
tile
|> neighbors()
|> Enum.any?(& &1 not in region)
end)
end
def area(region) do
MapSet.size(region)
end
def perimeter(region) do
region
|> border_tiles()
|> Enum.map(fn tile ->
tile
|> neighbors()
|> Enum.count(& &1 not in region)
end)
|> Enum.sum()
end
def sides(region) do
border_tiles =
region
|> border_tiles()
|> Enum.flat_map(fn tile ->
acc = (up(tile) in region) && [] || [{tile, :up}]
acc = (down(tile) in region) && acc || [{tile, :down} | acc]
acc = (left(tile) in region) && acc || [{tile, :left} | acc]
(right(tile) in region) && acc || [{tile, :right} | acc]
end)
|> MapSet.new()
count_sides(border_tiles, 0)
end
defp count_sides(border_tiles, acc) do
if MapSet.size(border_tiles) == 0 do
acc
else
border_tiles = remove_border(border_tiles, Enum.min(border_tiles))
count_sides(border_tiles, acc + 1)
end
end
defp remove_border(border_tiles, {tile, dir} = entry) when dir in [:up, :down] do
if entry in border_tiles do
border_tiles = MapSet.delete(border_tiles, entry)
remove_border(border_tiles, {right(tile), dir})
else
border_tiles
end
end
defp remove_border(border_tiles, {tile, dir} = entry) when dir in [:left, :right] do
if entry in border_tiles do
border_tiles = MapSet.delete(border_tiles, entry)
remove_border(border_tiles, {down(tile), dir})
else
border_tiles
end
end
defp neighbors(tile) do
[
up(tile),
down(tile),
left(tile),
right(tile)
]
end
defp up({i, j}) do
{i - 1, j}
end
defp down({i, j}) do
{i + 1, j}
end
defp left({i, j}) do
{i, j - 1}
end
defp right({i, j}) do
{i, j + 1}
end
defp regions(garden, acc) when map_size(garden) == 0 do
acc
end
defp regions(garden, acc) do
{tile, plant} = Enum.at(garden, 0)
{region, garden} = dfs_region(garden, plant, tile, [])
regions(garden, [region | acc])
end
defp dfs_region(garden, plant, tile, acc) do
if garden[tile] == plant do
garden = Map.delete(garden, tile)
acc = [tile | acc]
for neighbor <- neighbors(tile), reduce: {acc, garden} do
{acc, garden} -> dfs_region(garden, plant, neighbor, acc)
end
else
{acc, garden}
end
end
end
import AoC2024.Day12
regions = regions(garden)
for region <- regions, reduce: 0 do
total -> total + area(region) * perimeter(region)
end
for region <- regions, reduce: 0 do
total -> total + area(region) * sides(region)
end