Advent 2024 - Day 12
Mix.install([
{:kino, "~> 0.14.2"}
])
Setup
input = Kino.Input.textarea("Please paste your input file")
input = input
|> Kino.Input.read()
|> String.split("\n")
|> Enum.map(&String.graphemes/1)
defmodule McMasterCarr do
def get_surronding({x, y}) do
[
{x, y - 1},
{x + 1, y},
{x, y + 1},
{x - 1, y}
]
end
def get_valid_surronding(board, seen, pos, val) do
for {x, y} <- get_surronding(pos) do
if x < 0 or y < 0 or x >= length(board) or y >= length(board) do
nil
else
if board |> Enum.at(y) |> Enum.at(x) == val do
if MapSet.member?(seen, {x, y}) do
nil
else
{x, y}
end
else
nil
end
end
end
|> Enum.filter(&(&1))
end
def cell_value(board, {x, y}) do
if x < 0 or y < 0 or y >= length(board) or x >= board |> Enum.at(0) |> length do
nil
else
board |> Enum.at(y) |> Enum.at(x)
end
end
def find_chunks(board, pos, seen) do
val = cell_value(board, pos)
pieces = [pos]
get_valid_surronding(board, seen, pos, val)
|> Enum.reduce({pieces, seen}, fn pos, {pieces, seen} ->
seen = MapSet.put(seen, pos)
{new_pieces, seen} = find_chunks(board, pos, seen)
pieces = pieces ++ new_pieces
{pieces, seen}
end)
end
def find_all_chunks(board) do
positions = for x <- 0..(length(board) - 1) do
for y <- 0..(length(board) - 1) do
{x, y}
end
end
|> List.flatten()
{_seen, chunks} = Enum.reduce(positions, {MapSet.new(), []}, fn pos, {seen, chunks} ->
if MapSet.member?(seen, pos) do
{seen, chunks}
else
seen = MapSet.put(seen, pos)
{pieces, seen} = find_chunks(board, pos, seen)
chunks = [MapSet.new(pieces) | chunks]
{seen, chunks}
end
end)
chunks
end
end
Part 1
McMasterCarr.find_all_chunks(input)
|> Enum.map(fn pieces ->
fences = pieces
|> Enum.map(fn piece ->
McMasterCarr.get_surronding(piece)
|> Enum.map(fn pos ->
not MapSet.member?(pieces, pos)
end)
|> Enum.filter(&(&1))
|> Enum.count()
end)
|> Enum.sum()
MapSet.size(pieces) * fences
end)
|> Enum.sum()
Part 2
defmodule Grainger do
def get_surronding({{x, y}, direction}) do
if direction == 0 or direction == 2 do
[
{{x + 1, y}, direction},
{{x - 1, y}, direction},
]
else
[
{{x, y - 1}, direction},
{{x, y + 1}, direction}
]
end
end
def get_valid_surronding(fences, seen, fence) do
for fence <- get_surronding(fence) do
if MapSet.member?(seen, fence) or not MapSet.member?(fences, fence) do
nil
else
fence
end
end
|> Enum.filter(&(&1))
end
def find_chunks(fences, fence, seen) do
pieces = MapSet.new([fence])
get_valid_surronding(fences, seen, fence)
|> Enum.reduce({pieces, seen}, fn fence, {pieces, seen} ->
seen = MapSet.put(seen, fence)
{new_pieces, seen} = find_chunks(fences, fence, seen)
pieces = MapSet.union(pieces, new_pieces)
{pieces, seen}
end)
end
def combine_fences(fences) do
{_seen, chunks} = Enum.reduce(fences, {MapSet.new(), []}, fn fence, {seen, chunks} ->
if MapSet.member?(seen, fence) do
{seen, chunks}
else
seen = MapSet.put(seen, fence)
{pieces, seen} = find_chunks(fences, fence, seen)
chunks = [pieces | chunks]
{seen, chunks}
end
end)
chunks
end
end
McMasterCarr.find_all_chunks(input)
|> Enum.map(fn pieces ->
fences = pieces
|> Enum.map(fn piece ->
McMasterCarr.get_surronding(piece)
|> Enum.with_index()
|> Enum.filter(fn {pos, _index} ->
not MapSet.member?(pieces, pos)
end)
end)
|> List.flatten()
|> Enum.group_by(fn {_pos, direction} -> direction end)
|> Enum.map(fn {_direction, fences} ->
Grainger.combine_fences(fences |> MapSet.new())
|> Enum.count()
end)
|> Enum.sum()
MapSet.size(pieces) * fences
end)
|> Enum.sum()