Advent of Code 2025 - Day 4

I already had a module for working with 2D arrays defined for prior AOC runs, so today’s was straightforward:

defmodule Y2025.Day04 do
  def parse(s) do
    s
    |> String.split("\n")
    |> Enum.map(&String.graphemes/1)
    |> Array.from_list()
  end

  def part1(s) do
    a = parse(s)

    a
    |> Array.map_all(fn x, y, v -> removable?(a, x, y, v) end)
    |> Array.to_list()
    |> Enum.count(& &1)
  end

  def removable?(a, x, y, v) do
    v == "@" &&
      Array.neighbors(a, {x, y}) # gives the indexes of all valid neighbors
      |> Enum.map(&Array.get(a, &1))
      |> Enum.count(&(&1 == "@")) < 4
  end

  def part2(s) do
    a = parse(s)
    b = fixed_point(&remove/1, a)

    count(a) - count(b)
  end

  def fixed_point(f, x) do
    do_fixed_point(f, count(x), f.(x))
  end

  def do_fixed_point(f, old_count, a) do
    new_count = count(a)

    if new_count == old_count do
      a
    else
      do_fixed_point(f, new_count, f.(a))
    end
  end

  def remove(a) do
    Array.map_all(a, fn x, y, v -> if removable?(a, x, y, v), do: ".", else: v end)
  end

  def count(a) do
    Array.to_list(a) |> Enum.count(&(&1 == "@"))
  end
end