Powered by AppSignal & Oban Pro

--- Day 4: Printing Department ---

2025/day_4.livemd

— Day 4: Printing Department —

Mix.install([{:kino_aoc, "~> 0.1"}])

Setup

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2025", "4", System.fetch_env!("LB_AOC_SESSION_COOKIE"))
test_input = Kino.Input.textarea("test_input")
test_input = Kino.Input.read(test_input)
defmodule PrintingDeptartment do
  @cardinal_directions [:n, :ne, :e, :se, :s, :sw, :w, :nw]

  def parse_to_grid(input) do
    input
    |> String.split()
    |> Enum.with_index(&{&2, &1})
    |> Enum.map(fn {index, rolls} ->
      {index, rolls |> String.graphemes() |> Enum.with_index(&{&2, &1}) |> Map.new()}
    end)
    |> Map.new()
  end

  def max_iterations(input) do
    ~r(@)
    |> Regex.scan(input)
    |> List.flatten()
    |> length()
  end

  def cardinal_directions(), do: @cardinal_directions

  @spec check_adjacent(map(), integer(), integer(), atom()) :: String.t() | nil
  def check_adjacent(grid, col, row, :n), do: grid[col][row - 1]
  def check_adjacent(grid, col, row, :ne), do: grid[col + 1][row - 1]
  def check_adjacent(grid, col, row, :e), do: grid[col + 1][row]
  def check_adjacent(grid, col, row, :se), do: grid[col + 1][row + 1]
  def check_adjacent(grid, col, row, :s), do: grid[col][row + 1]
  def check_adjacent(grid, col, row, :sw), do: grid[col - 1][row + 1]
  def check_adjacent(grid, col, row, :w), do: grid[col - 1][row]
  def check_adjacent(grid, col, row, :nw), do: grid[col - 1][row - 1]
end

Part 1

import PrintingDeptartment

grid = parse_to_grid(puzzle_input)

for {col_index, row} <- grid, {row_index, "@"} <- row, reduce: 0 do
  count ->
    adjacent_rolls =
      cardinal_directions()
      |> Enum.map(&amp;check_adjacent(grid, col_index, row_index, &amp;1))
      |> Enum.sum_by(&amp;if(&amp;1 == "@", do: 1, else: 0))

    if adjacent_rolls < 4, do: count + 1, else: count
end

Part 2

import PrintingDeptartment

grid = parse_to_grid(puzzle_input)
max_iterations = max_iterations(puzzle_input)

Enum.reduce_while(0..max_iterations, {0, grid}, fn _, {starting_count, grid} ->
  {finishing_count, remove_rolls} =
    for {col_index, row} <- grid, {row_index, "@"} <- row, reduce: {starting_count, []} do
      {count, remove_rolls} ->
        adjacent_rolls =
          cardinal_directions()
          |> Enum.map(&amp;check_adjacent(grid, col_index, row_index, &amp;1))
          |> Enum.sum_by(&amp;if(&amp;1 == "@", do: 1, else: 0))

        if adjacent_rolls < 4 do
          {count + 1, [[col_index, row_index] | remove_rolls]}
        else
          {count, remove_rolls}
        end
    end

  grid = Enum.reduce(remove_rolls, grid, &amp;put_in(&amp;2, &amp;1, "."))

  if finishing_count == starting_count do
    {:halt, finishing_count}
  else
    {:cont, {finishing_count, grid}}
  end
end)