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

Advent of Code 2024

2024/day12.livemd

Advent of Code 2024

Mix.install([
  {:req, "~> 0.5.8"},
  {:kino, "~> 0.14.2"}
])

Day 12

Kino.configure(inspect: [charlists: :as_lists])
input =
  "https://adventofcode.com/2024/day/12/input"
  |> Req.get!(headers: [cookie: "session=#{System.get_env("AOC_COOKIE")}"])
  |> Map.get(:body)
sample =
  """
  AAAA
  BBCD
  BBCC
  EEEC
  """
sample2 = """
OOOOO
OXOXO
OOOOO
OXOXO
OOOOO
"""
sample3 =
  """
  RRRRIICCFF
  RRRRIICCCF
  VVRRRCCFFF
  VVRCCCJFFF
  VVVVCJJCFE
  VVIVCCJJEE
  VVIIICJJEE
  MIIIIIJJEE
  MIIISIJEEE
  MMMISSJEEE
  """
sample4 = """
EEEEE
EXXXX
EEEEE
EXXXX
EEEEE
"""
defmodule Day12 do
  def parse(input) do
    lines =
      input
      |> String.split("\n", trim: true)
      |> Enum.map(&String.split(&1, "", trim: true))

    for {row, y} <- Enum.with_index(lines),
        {val, x} <- Enum.with_index(row),
        into: %{},
        do: {{x, y}, val}
  end

  def total_price(grid) do
    grid
    |> Enum.reduce({0, MapSet.new()}, fn {k, v}, {total, seen} ->
      if MapSet.member?(seen, k) do
        {total, seen}
      else
        {price, seen} =
          calc_region([k], v, grid, seen, 0, 0)

        {price + total, seen}
      end
    end)
    |> elem(0)
  end

  def calc_region([], _, _, seen, perimeter, area), do: {perimeter * area, seen}

  def calc_region([pos | rest], plant, grid, seen, perimeter, area) do
    if MapSet.member?(seen, pos) do
      calc_region(rest, plant, grid, seen, perimeter, area)
    else
      seen = MapSet.put(seen, pos)
      area = area + 1

      {x, y} = pos

      {to_visit, perimeter} =
        [
          {1, 0},
          {-1, 0},
          {0, 1},
          {0, -1}
        ]
        |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
        |> Enum.reduce({rest, perimeter}, fn pos, {to_visit, perimeter} ->
          cond do
            Map.get(grid, pos) != plant ->
              {to_visit, perimeter + 1}

            MapSet.member?(seen, pos) ->
              {to_visit, perimeter}

            true ->
              {[pos | to_visit], perimeter}
          end
        end)

      calc_region(to_visit, plant, grid, seen, perimeter, area)
    end
  end

  def find_regions(grid) do
    grid
    |> Enum.reduce({[], MapSet.new()}, fn {k, v}, {regions, seen} ->
      if MapSet.member?(seen, k) do
        {regions, seen}
      else
        {region, seen} =
          find_region([k], v, grid, seen, MapSet.new())

        {[region | regions], seen}
      end
    end)
    |> elem(0)
  end

  def find_region([], _, _, seen, region), do: {region, seen}

  def find_region([pos | rest], plant, grid, seen, region) do
    if MapSet.member?(seen, pos) do
      find_region(rest, plant, grid, seen, region)
    else
      seen = MapSet.put(seen, pos)
      region = MapSet.put(region, pos)

      {x, y} = pos

      to_visit =
        [
          {1, 0},
          {-1, 0},
          {0, 1},
          {0, -1}
        ]
        |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
        |> Enum.filter(&amp;(Map.get(grid, &amp;1) == plant))
        |> Enum.reduce(rest, fn pos, to_visit -> [pos | to_visit] end)

      find_region(to_visit, plant, grid, seen, region)
    end
  end
end
import Day12

Part 1

input
|> parse()
|> total_price()

Part 2

input
|> parse()
|> find_regions()
|> Enum.map(fn region ->
  area = MapSet.size(region)

  # region |> IO.inspect()

  exterior_corners =
    region
    |> Enum.to_list()
    |> Enum.map(fn {x, y} ->
      # is upper right corner?
      ur? = !MapSet.member?(region, {x - 1, y}) &amp;&amp; !MapSet.member?(region, {x, y - 1})
      ul? = !MapSet.member?(region, {x + 1, y}) &amp;&amp; !MapSet.member?(region, {x, y - 1})
      br? = !MapSet.member?(region, {x - 1, y}) &amp;&amp; !MapSet.member?(region, {x, y + 1})
      bl? = !MapSet.member?(region, {x + 1, y}) &amp;&amp; !MapSet.member?(region, {x, y + 1})

      [ur?, ul?, br?, bl?]
      |> Enum.filter(&amp; &amp;1)
      |> Enum.count()
    end)
    |> Enum.sum()

  interior_corners =
    region
    |> Enum.to_list()
    |> Enum.map(fn {x, y} ->
      # is upper right corner?
      ur? =
        MapSet.member?(region, {x - 1, y}) &amp;&amp; MapSet.member?(region, {x, y + 1}) &amp;&amp;
          !MapSet.member?(region, {x - 1, y + 1})

      ul? =
        MapSet.member?(region, {x + 1, y}) &amp;&amp; MapSet.member?(region, {x, y + 1}) &amp;&amp;
          !MapSet.member?(region, {x + 1, y + 1})

      br? =
        MapSet.member?(region, {x - 1, y}) &amp;&amp; MapSet.member?(region, {x, y - 1}) &amp;&amp;
          !MapSet.member?(region, {x - 1, y - 1})

      bl? =
        MapSet.member?(region, {x + 1, y}) &amp;&amp; MapSet.member?(region, {x, y - 1}) &amp;&amp;
          !MapSet.member?(region, {x + 1, y - 1})

      [ur?, ul?, br?, bl?]
      |> Enum.filter(&amp; &amp;1)
      |> Enum.count()
    end)
    |> Enum.sum()

  {area, exterior_corners + interior_corners}
  # |> IO.inspect(label: "ex_#{exterior_corners} in_#{interior_corners}")
end)
# |> IO.inspect()
|> Enum.map(fn {a, b} -> a * b end)
|> Enum.sum()