Powered by AppSignal & Oban Pro

Advent of code 2025 - Day 4

day04.livemd

Advent of code 2025 - Day 4

Description

Day 4 - Printing Department

defmodule Load do
  def input do
    File.read!("#{__DIR__}/inputs/day04.txt")
  end
end
defmodule Day4 do
  defp parse(input) do
    input
    |> String.split("\n")
    |> Enum.filter(&(&1 != ""))
  end

  defp parse_to_grid_map(input) do
    grid = parse(input)

    for {row, r} <- Enum.with_index(grid),
        {char, c} <- row |> String.graphemes() |> Enum.with_index(),
        into: %{} do
      {{r, c}, char}
    end
  end

  defp find_removable(grid_map) do
    grid_map
    |> Enum.filter(fn {{r, c}, char} ->
      char == "@" and count_adjacent_at(grid_map, r, c) < 4
    end)
    |> Enum.map(fn {pos, _} -> pos end)
  end

  defp remove_positions(grid_map, positions) do
    Enum.reduce(positions, grid_map, fn pos, acc ->
      Map.put(acc, pos, ".")
    end)
  end

  defp count_adjacent_at(grid_map, row, col) do
    for dr <- -1..1, dc <- -1..1, dr != 0 or dc != 0, reduce: 0 do
      count ->
        if Map.get(grid_map, {row + dr, col + dc}) == "@",
          do: count + 1,
          else: count
    end
  end

  def part1(input) do
    input |> parse_to_grid_map() |> find_removable() |> length()
  end

  def part2(input) do
    input |> parse_to_grid_map() |> remove_until_stable()
  end

  defp remove_until_stable(grid_map, total_removed \\ 0) do
    case find_removable(grid_map) do
      [] ->
        total_removed

      positions ->
        grid_map
        |> remove_positions(positions)
        |> remove_until_stable(total_removed + length(positions))
    end
  end
end
ExUnit.start(autorun: false)

defmodule Test do
  use ExUnit.Case, async: true

  @input """
  ..@@.@@@@.
  @@@.@.@.@@
  @@@@@.@.@@
  @.@@@@..@.
  @@.@@@@.@@
  .@@@@@@@.@
  .@.@.@.@@@
  @.@@@.@@@@
  .@@@@@@@@.
  @.@.@@@.@.
  """

  test "part 1" do
    assert Day4.part1(@input) == 13
  end

  test "part 2" do
    assert Day4.part2(@input) == 43
  end
end

ExUnit.run()
Day4.part1(Load.input())
Day4.part2(Load.input())