Day 04
Mix.install([:kino_aoc])
Parse
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2025", "4", System.fetch_env!("LB_ADVENT_OF_CODE_SESSION"))
rolls =
puzzle_input
|> String.split("\n", trim: true)
|> Enum.with_index()
|> Enum.flat_map(fn {line, row} ->
line
|> String.to_charlist()
|> Enum.with_index()
|> Enum.filter(&(elem(&1, 0) == ?@))
|> Enum.map(&{elem(&1, 1), row})
end)
|> MapSet.new()
Implementation
defmodule PaperRolls do
defp adjacent({x, y}) do
for dx <- -1..1,
dy <- -1..1,
{dx, dy} != {0, 0},
do: {x + dx, y + dy}
end
def movable?(pos, map) do
pos
|> adjacent()
|> Enum.count(&(&1 in map))
|> then(&(&1 < 4))
end
end
Part 1
Enum.count(rolls, &PaperRolls.movable?(&1, rolls))
Part 2
cleaned =
Stream.repeatedly(fn -> [] end)
|> Enum.reduce_while(rolls, fn _, acc ->
removable =
Enum.filter(acc, &PaperRolls.movable?(&1, acc))
|> MapSet.new()
case MapSet.difference(acc, removable) do
^acc -> {:halt, acc}
remaining -> {:cont, remaining}
end
end)
MapSet.size(rolls) - MapSet.size(cleaned)