Powered by AppSignal & Oban Pro

Day 4

2025/04.livemd

Day 4

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

Setup

{:ok, input} = KinoAOC.download_puzzle("2025", "4", System.fetch_env!("LB_AOC_SESSION"))
{:ok,
 "@..@@.@.@@@....@@..@@@.@@@@@@..@..@@.@.@@.@@...@.@.@...@@.....@..@@.@@@@.@@@.@@@..@.@@@@@@.@@@@@@@@@@...@@..@..@@.@@@@@@..@@@...@.@@@@@@\n@.@@.@@@@@@..@.@@@@.@@@@@@.@.@@@@..@@@@@@@@.@.@@@...@.@@@@@@..@..@.@@@.@@@@.@@@@.@@@@@@@@@@@..@@@@@@.@@.@@.@.@@@@.@@@@@@@@@@...@.@@.....\n@@.@@@@@.@.@@.@@@@@@@@..@@@@@.@@.@@.@@@@.@..@@@.@.@@@@@@@@.@.@@@@@@@@@@@...@@@@@@.@@@@@@@.@@@@.@@.@@.@@.@@.@@.@@@..@@..@@.@@@@@@.....@.@\n@@@@.@..@@@.@@.@@.@@.@@@@.@.@.@@@@@@@.@@.@@@...@.@@.@@@@@@.@@@@@.@@.@@@@@.@@..@.@@@@@@@.@@@@.@.@@@.@...@@.@@@@.@.@@@@.@.@.@@@@.@@.@@@@@.\n.@@@@.@@@.@@.@@@.@@.@.@.@@..@.@@..@.@@.@@@@@@@@.@.@.@.@@.@@@@..@@.@..@..@.@@@@@@@.@@@.@.@@@@@..@@@@@@@.@@@.@@.@@..@@...@@..@.@@@.@@@@@.@\n@@@@@@@.@@.@.@.@@@.@@.@@@@@@@@..@@@@.@@.@@@..@@..@@@@@.@@@.@.@.@..@...@@.@..@@@.@.@@.@.@.@@.@@@@@@.@..@.@@@.@..@..@@..@@@@.@@.@@@@@@@.@.\n..@@@.@@.@...@.@@@.@@.@.@@@@@@@@@.@@.@..@.@@@.@@@@.@.@.@@@.@@@.@.@@@@@@.@@.@...@@@..@@.@.@.@@@.@@.@.@@@..@.@@@@@..@.@@.@..@.@@.@...@@@..\n@@.@.@.@@..@@@@.@.@@.@@@@@@@@.@.@@.@.@@@..@..@@@@@@@...@@@@@@.@@@@@@.@.@@@@@@@@...@.@@@@@@@@@@@.@@@..@.@.@@@.@@@.@..@@@.@..@@.@.....@@.@\n@@.@.@@@@.@@@@..@..@.@@.@@.@@..@.@@@...@@@.@@.@.@@@@..@@.@.@@@.@@@@..@@@.@.@@@..@@...@@.@....@@@.@@@@@@@..@@.....@@.@@@@..@.@@@.@@@.@@@@\n@.@@@@..@@@@..@..@@@@@@.@@..@.@.@@@@@..@@@.@@@.@.@@@@..@@@@.@.@@.....@@@@@@@@@@.@..@@.@@@@@@@@.@@@..@..@.@@@@....@.@.@@@@.@@.@.@.@.@@@@.\n@.@@....@.@@@@@..@@@@..@@.@@@.@@@.@.@..@@@.@@.@@.@@@@@@@@.@@@.@@.@..@@@@..@@@@@@@@@@.@@@...@@@@.@@.@@@@@.@@@..@@..@@.@@@.@@@@@@@@.@@.@@@\n@@@.@@.@.@.@.@@.@@@..@@.@..@@@..@@@@..@@@@..@.@@@@@@@.@@@@..@.@@..@@.@..@@....@@@@@@@@@@@@@.@..@@@@..@@@@@.@@@.@...@@.@..@.@@@@@@@.@@.@@\n.@...@.@@@.@@@.@.@@.@...@@.@@.@@.@.@@..@@.@.@@.@.@@@@@@@.@.@@@@.@@@@@@@.@@@..@@@@@@..@@@.@@.@.@..@@@@@..@.@@@@...@..@@@@@@@.@@@@@@@.@.@@\n@@@@.@.@.@.@.@.@@@@@@.@@@@@@@..@@@@..@@.@....@..@.@@..@@@@@@@.@@@@@@@@...@@@..@@@@..@@@@@.@@@@.@@..@@.@@.@@@@.@@..@@@@@@@@.@@.@@@@@...@@\n@@.@.@@@..@....@@@@@.@@..@@@...@...@@@.@@@@@@..@@@@@@@@.@@@@@@..@.@@...@.@@.@..@@..@@@@.@..@..@@@.@.@@@.@.@@@@@.@@@@@.@.@@..@@@@..@.@.@.\n@.@@@@.@..@@.@..@.@@.@.@@@@..@.@.@..@@@@.@@@.@@.@@.@@@@@@.@......@..@@.@@@...@...@@@.@@@.@@.@@@.@@..@.@.@@@@..@@@@@.@@@@@@.@@..@@@@@@@@@\n@.@@@@@@@@.@.@@.@.@.@...@@@@@..@@.@.@@@@@@@.@@...@@....@@@..@@.@@@@.@.@@@@@.@@@@.@@..@.@@@....@@.@.@@@.@@@@@.@@@@@@.@@@@@..@...@....@@@.\n@.@@@@.@..@@@@@@@@@@@@@@..@@@@...@@@@@@@@..@@@@@.@@@@...@@.@@@@.@@@@@@@.@@..@.@@..@@@.@......@@@@@@@@@@.@@@@@@@..@@@@@..@..@@@@@@.@@@@@.\n@@..@@..@@@@@@@@@@.@...@@.@@@@@@@@..@..@@@.@.@.@.@@.@@@@@@@@@@@..@.@..@@@.@....@@@.@@@@.@.@@@.@@@.@@..@@@.@.@@@@@..@@.@...@@@@@@@.@@@...\n@..@@.@@.@@@@.@..@@.@@@@@@@@.@@.@.@@@@@..@.@@.@..@.@@.@@@@@@@@@@@...@.@@@.@@@.@.@@@@@@@@@@..@.@@@@@@..@@@@@.@.@@.@@@@..@@@@@....@@..@@.@\n..@@.@..@@..@@.@.@.@@@.@..@.@@@@@@@@@..@@@@@.@@@@@@.@@.@@.@..@@@@@@@..@...@..@@..@@.@@@.@..@@@@.@..@@@.@@@.@@@.@.@@@@@@@.@.@.@@@.@@@@...\n...@@....@.@@@@@@@@...@@@@@@@.@.@@@@.@@@...@@@@@@@@@@@@@@@@@@@@@@@@.@..@@@@@@.@@@@..@@.@@...@@@@..@..@@@@@@@...@.@@.@@.@@..@.@.@.@.@@@@.\n.@@@.@@@.@..@@.@.@..@.@@...@@.@@.@.@@..@@.@...@..@@@@@@..@@@@.@@.@..@@@.@@.@.@.@.@@.@@@.@@@.@..@.@@@.@.@@@..@@.@.@@@.@.@@@@@.@@@@@...@@@\n@@.@..@@.@@@@@@@@@@@@...@@.@@@@.@@@@..@..@..@@@@@@@.@.@@..@@@.@@@.@@@@@@@@@@@.@@@@...@@.@.@.@@..@.@@.@....@..@@.@.@@..@@@@@@...@@.@@@.@@\n@.@@.@@.@@@@@..@..@@@@@@@@..@..@@.....@@@@.@.@@@@@.@@@.@@@@@@.@@@@.@.@.@.@@.@.@@.@@.@@@@@.@@@.@..@.@@...@.@@@@@@@@@..@@@...@.@.@.@.@@@@.\n.....@@@@@@@.@@.@.@...@@@..@..@@.@@@@.....@..@.@@@@@.@@.@@...@@.@.@@.@@@.@@@.@@@@@@.@.@@@@@@..@@@@@@@@...@..@.@..@@@..@..@@.@....@@@@@@@\n@@@@.@@@...@@.@.@@.@@.@.@@.@@@@.@@@@@@@@@@.@@@.@..@@.@@@.@@.@..@..@@@@.@..@@@@.@@@@...@@.@@.@..@@@@@@@.@@..@@@.@@@@@@.@@@.@@@...@.@@@@.@\n.@.@@.....@.@@@@@@@...@..@@.@@..@.@.@@..@..@@..@@@@@@@...@@..@@@.@.@@@..@@.@@...@@..@@@@@@.@...@..@.@@@@@@@@@.@@.@@.@...@@@@@@@.@@@@@..@\n@.@@@@@...@.@@@@@@@...@@.@.@@@@@@.@@@@@@..@..@.@.@...@@....@@@.@...@@@@@@....@@@..@@@@.@@..@.@@@@....@@@@@@@@@@@@@.@.@@.@@@@@@.@.@..@.@@\n@@@@.@@@@@.@..@@@@@@@.@@@@@@@@@@@@@.@.@@.@@@@.@@@@@..@@@@@@.@....@@@@@@@@.@@@@@@.@.@..@@@@.@.....@.@.@@@@@@@@@@@@@@@@@@@@@." <> ...}
ex = """
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
"""
"..@@.@@@@.\n@@@.@.@.@@\n@@@@@.@.@@\n@.@@@@..@.\n@@.@@@@.@@\n.@@@@@@@.@\n.@.@.@.@@@\n@.@@@.@@@@\n.@@@@@@@@.\n@.@.@@@.@.\n"

Solution

defmodule Day4 do
  @neighbors [
    {-1,  0}, # left
    { 1,  0}, # right
    {-1, -1}, # top left
    { 1, -1}, # top right
    {-1,  1}, # bottom left
    { 1,  1}, # bottom right
    { 0, -1}, # top
    { 0,  1}  # bottom
  ]
  
  def parse(str) do
    String.split(str) 
      |> Enum.with_index()
      |> Enum.flat_map(fn {row, y} -> 
        String.split(row, "", trim: true) 
          |> Enum.with_index()
        |> Enum.map(fn {v, x} ->
          {v, {x, y}}
        end)
      end)
    |> Enum.reduce(%{}, fn {v, pos}, map ->
      Map.put(map, pos, {v, pos})
    end)
  end
  
  def adj(grid, {x, y}) do
    @neighbors
      |> Enum.map(fn {dx, dy} ->
          Map.get(grid, {x + dx, y + dy}, nil)  
        end)
      |> Enum.filter(&amp; &amp;1) # truthy values only please
  end

  def rollcount(grid, pos, max) do
    grid 
      |> adj(pos)
      |> Enum.count_until(fn {v, _} -> v == "@" || v == "x" end, max)
  end

  def print(grid) do
    {max_x, _} = grid |> Map.keys() |> Enum.max_by(fn {x, _} -> x end)
    {_, max_y} = grid |> Map.keys() |> Enum.max_by(fn {_, y} -> y end)
    
    for y <- 0..max_y do
      for x <- 0..max_x do
        {v, _pos } = Map.get(grid, {x, y}, {".", {x, y}})
        IO.write(v)
      end
      IO.puts("")
    end
  end

  def forklift_accessible_rolls(grid) do
    grid |> Map.values |> Enum.filter(fn
      {"@", pos } -> rollcount(grid, pos, 5) < 4
      { _v, _pos } -> false
    end)
  end

  def solve(str) do
    parse(str) 
      |> forklift_accessible_rolls()
      |> Enum.count()
  end

  def remove_rolls(grid), do: remove_rolls(grid, 0)

  def remove_rolls(grid, removed_count_acc) do
    to_remove = grid |> forklift_accessible_rolls()

    removed_count = length(to_remove)
    if removed_count > 0 do
      Enum.reduce(to_remove, grid, fn {_v, pos}, acc -> Map.delete(acc, pos) end)
        |> remove_rolls(removed_count_acc + removed_count)
    else
      { grid, removed_count_acc }
    end
  end
  

  def solve2(str) do
    grid = parse(str)
    {_, total_removed } = remove_rolls(grid)
    total_removed
  end
end
{:module, Day4, <<70, 79, 82, 49, 0, 0, 25, ...>>, {:solve2, 1}}

Part 1

Day4.parse(ex)
%{
  {1, 3} => {".", {1, 3}},
  {9, 9} => {".", {9, 9}},
  {1, 1} => {"@", {1, 1}},
  {1, 5} => {"@", {1, 5}},
  {8, 9} => {"@", {8, 9}},
  {5, 0} => {"@", {5, 0}},
  {5, 7} => {".", {5, 7}},
  {1, 8} => {"@", {1, 8}},
  {5, 4} => {"@", {5, 4}},
  {4, 5} => {"@", {4, 5}},
  {6, 1} => {"@", {6, 1}},
  {6, 0} => {"@", {6, 0}},
  {9, 4} => {"@", {9, 4}},
  {9, 1} => {"@", {9, 1}},
  {3, 4} => {"@", {3, 4}},
  {8, 4} => {"@", {8, 4}},
  {4, 9} => {"@", {4, 9}},
  {7, 7} => {"@", {7, 7}},
  {6, 9} => {"@", {6, 9}},
  {5, 8} => {"@", {5, 8}},
  {8, 7} => {"@", {8, 7}},
  {9, 2} => {"@", {9, 2}},
  {5, 3} => {"@", {5, 3}},
  {0, 5} => {".", {0, 5}},
  {3, 3} => {"@", {3, 3}},
  {4, 6} => {".", {4, 6}},
  {6, 2} => {"@", {6, 2}},
  {3, 7} => {"@", {3, 7}},
  {9, 8} => {".", {9, 8}},
  {7, 0} => {"@", {7, 0}},
  {1, 6} => {"@", {1, 6}},
  {7, 4} => {".", {7, 4}},
  {3, 1} => {".", {3, 1}},
  {2, 5} => {"@", {2, 5}},
  {7, 6} => {"@", {7, 6}},
  {4, 4} => {"@", {4, 4}},
  {7, 2} => {".", {7, 2}},
  {3, 9} => {".", {3, 9}},
  {9, 6} => {"@", {9, 6}},
  {6, 5} => {"@", {6, 5}},
  {1, 0} => {".", {1, 0}},
  {2, 2} => {"@", {2, 2}},
  {8, 3} => {"@", {8, 3}},
  {8, 8} => {"@", {8, 8}},
  {0, 1} => {"@", {0, 1}},
  {7, 1} => {".", {7, 1}},
  {5, 1} => {".", {5, ...}},
  {7, 5} => {"@", {...}},
  {0, ...} => {"@", ...},
  {...} => {...},
  ...
}
Day4.parse(ex) |> Day4.adj({0,0})
[{".", {1, 0}}, {"@", {1, 1}}, {"@", {0, 1}}]
Day4.parse(ex) |> Day4.rollcount({2,0}, 10)
3
Day4.parse(ex) |> Day4.rollcount({0, 0}, 20)
2
Day4.parse(ex) |> Day4.rollcount({7, 0}, 5)
4
Day4.parse(ex) |> Day4.print()
..@@.@@@@.
@@@.@.@.@@
@@@@@.@.@@
@.@@@@..@.
@@.@@@@.@@
.@@@@@@@.@
.@.@.@.@@@
@.@@@.@@@@
.@@@@@@@@.
@.@.@@@.@.
[:ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok, :ok]
Day4.solve(ex)
13
Day4.solve(input)
1395

Part 2

Day4.solve(ex)
13
Day4.solve2(input)
8451