Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 12

2024/day12.livemd

Day 12

Mix.install([{:utils, path: "#{__DIR__}/utils"}])

Solution

defmodule Day12 do
  use Agent

  def init(grid) do
    lookup = Map.new(grid)
    Agent.start_link(fn -> lookup end, name: __MODULE__)
    Agent.update(__MODULE__, fn _ -> lookup end)
    grid
  end

  defp same(a, b) do
    a_val = Agent.get(__MODULE__, &Map.get(&1, a))
    b_val = Agent.get(__MODULE__, &Map.get(&1, b))
    a_val == b_val
  end

  defp nbrs({x, y}), do: [{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}]

  defp dfs(xy, region) do
    region_ = MapSet.put(region, xy)

    xy
    |> nbrs()
    |> Enum.reject(&(&1 in region_))
    |> Enum.filter(&same(&1, xy))
    |> Enum.reduce(region_, &dfs(&1, &2))
  end

  def find_region({xy, _}, {regions, seen}) do
    if xy in seen do
      {regions, seen}
    else
      region = dfs(xy, MapSet.new())
      {[region | regions], MapSet.union(seen, region)}
    end
  end

  def perimeter(region) do
    Enum.reduce(region, 0, fn xy, acc ->
      delta = xy |> nbrs() |> Enum.reject(&same(&1, xy)) |> length()
      acc + delta
    end)
  end

  defp corner_nbrs({x, y}) do
    rotate_xy = fn {x1, y1} -> {x + y1 - y, y - x1 + x} end

    [{x + 1, y}, {x, y + 1}, {x + 1, y + 1}]
    |> Stream.iterate(&Enum.map(&1, rotate_xy))
    |> Enum.take(4)
  end

  defp count_corners(xy, corner) do
    [nbr1, nbr2, diag] = Enum.map(corner, &same(&1, xy))
    outside = not (nbr1 or nbr2)
    inside = nbr1 and nbr2 and not diag
    Enum.count([inside, outside], & &1)
  end

  def sides(region) do
    Enum.reduce(region, 0, fn xy, acc ->
      delta = xy |> corner_nbrs() |> Enum.map(&count_corners(xy, &1)) |> Enum.sum()
      acc + delta
    end)
  end
end

{grid, _} = Utils.read_grid("day12")

grid
|> Day12.init()
|> Enum.reduce({[], MapSet.new()}, &Day12.find_region(&1, &2))
|> elem(0)
|> Enum.map(&(Enum.count(&1) * Day12.sides(&1)))
|> Enum.sum()