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

Advent of Code 2024 Day 12 Part 2

2024_day12_part2.livemd

Advent of Code 2024 Day 12 Part 2

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

Get Inputs

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2024", "12", System.fetch_env!("LB_SESSION"))

My answer

small_sample_input =
  """
  AAAA
  BBCD
  BBCC
  EEEC
  """
  |> String.trim()
parse_map = fn input ->
  input
  |> String.split("\n")
  |> Enum.with_index()
  |> Enum.flat_map(fn {row, row_index} ->
    row
    |> String.codepoints()
    |> Enum.with_index()
    |> Enum.map(fn {plant, col_index} ->
      {{row_index, col_index}, plant}
    end)
  end)
  |> Enum.into(%{})
end
map = parse_map.(small_sample_input)
defmodule Regions do
  @next_points [
    {:top, {-1, 0}},
    {:bottom, {1, 0}},
    {:left, {0, -1}},
    {:right, {0, 1}}
  ]

  @corner_points %{
    tl: {-1, -1},
    tr: {-1, 1},
    bl: {1, -1},
    br: {1, 1}
  }

  @initial_region %{
    price: 0,
    num_corners: 0,
    points: []
  }

  def get(map) do
    map_with_corners =
      map
      |> get_points_corners()
      |> get_num_corners()

    map_with_corners
    |> Enum.reduce(%{}, fn {_, %{plant: plant}} = area, acc_regions ->
      region = connect_point(area, map_with_corners, acc_regions)
      Map.put(acc_regions, plant, region)
    end)
  end

  def get_points_corners(map) do
    map
    |> Enum.into(%{}, fn {point, plant} ->
      {cur_r, cur_c} = point

      directions =
        @next_points
        |> Enum.reduce([], fn {direction, {mov_r, move_c}}, acc_directions ->
          next_point = {cur_r + mov_r, cur_c + move_c}

          case Map.get(map, next_point) do
            ^plant ->
              [direction | acc_directions]

            _ ->
              acc_directions
          end
        end)

      {
        point,
        %{
          plant: plant,
          corners: get_corners(directions),
          not_corners: get_not_corners(directions)
        }
      }
    end)
  end

  def get_corners(directions) do
    [
      {:tl, :top, :left},
      {:tr, :top, :right},
      {:bl, :bottom, :left},
      {:br, :bottom, :right}
    ]
    |> Enum.filter(fn {_, vertical, horizontal} ->
      if !Enum.member?(directions, vertical) and !Enum.member?(directions, horizontal) do
        :tl
      else
        nil
      end
    end)
    |> Enum.map(fn {corner, _, _} ->
      corner
    end)
  end

  def get_not_corners(directions) do
    [
      {:tl, :top, :left},
      {:tr, :top, :right},
      {:bl, :bottom, :left},
      {:br, :bottom, :right}
    ]
    |> Enum.filter(fn {_, vertical, horizontal} ->
      if Enum.member?(directions, vertical) and Enum.member?(directions, horizontal) do
        :tl
      else
        nil
      end
    end)
    |> Enum.map(fn {corner, _, _} ->
      corner
    end)
  end

  defp get_num_corners(map) do
    map
    |> Enum.into(%{}, fn {point, %{plant: plant, corners: corners, not_corners: not_corners}} ->
      num_outside_corners = length(corners)
      {cur_r, cur_c} = point

      num_inside_corners =
        not_corners
        |> Enum.count(fn not_coner ->
          {mov_r, move_c} = Map.get(@corner_points, not_coner)
          corner_point = {cur_r + mov_r, cur_c + move_c}

          case Map.get(map, corner_point) do
            nil -> false
            %{plant: target_plant} ->
              plant != target_plant
          end
        end)

      {
        point,
        %{
          plant: plant,
          num_corners: num_outside_corners + num_inside_corners
        }
      }
    end)
  end

  defp connect_point(area, map, acc_regions) do
    {point, %{plant: plant, num_corners: new_corners}} = area
    {cur_r, cur_c} = point

    new_points =
      @next_points
      |> Enum.reduce([], fn {_, {mov_r, move_c}}, acc_points ->
        next_point = {cur_r + mov_r, cur_c + move_c}

        case Map.get(map, next_point) do
          nil ->
            acc_points

          next_area ->
            if next_area.plant == plant do
              [next_point | acc_points]
            else
              acc_points
            end
        end
      end)

    new_points = [point | new_points]

    case Map.get(acc_regions, plant) do
      nil ->
        [
          %{
            price: 1,
            num_corners: new_corners,
            points: new_points
          }
        ]

      sub_regions ->
        connected_regions =
          sub_regions
          |> Enum.filter(fn %{points: points} ->
            Enum.any?(new_points, fn point ->
              Enum.member?(points, point)
            end)
          end)

        not_connected_regions = sub_regions -- connected_regions

        merged_region =
          connected_regions
          |> merge_sub_regions()
          |> then(fn %{price: price, num_corners: num_corners, points: points} ->
            %{
              price: price + 1,
              num_corners: num_corners + new_corners,
              points: Enum.uniq(new_points ++ points)
            }
          end)

        [merged_region | not_connected_regions]
    end
  end

  defp merge_sub_regions(connected_regions) do
    connected_regions
    |> Enum.reduce(@initial_region, fn sub_region, acc_sub_region ->
      %{
        price: acc_sub_region.price + sub_region.price,
        num_corners: acc_sub_region.num_corners + sub_region.num_corners,
        points: acc_sub_region.points ++ sub_region.points
      }
    end)
  end
end
Regions.get_corners([:left])
Regions.get_not_corners([:top, :left])
regions = Regions.get(map)
sum_price = fn regions ->
  regions
  |> Enum.map(fn {_, sub_regions} ->
    sub_regions
    |> Enum.map(fn %{price: price, num_corners: num_corners} ->
      price * num_corners
    end)
    |> Enum.sum()
  end)
  |> Enum.sum()
end
sum_price.(regions)
"""
EEEEE
EXXXX
EEEEE
EXXXX
EEEEE
"""
|> String.trim()
|> parse_map.()
|> Regions.get()
|> IO.inspect()
|> sum_price.()
"""
AAAAAA
AAABBA
AAABBA
ABBAAA
ABBAAA
AAAAAA
"""
|> String.trim()
|> parse_map.()
|> Regions.get()
|> IO.inspect()
|> sum_price.()
"""
RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
"""
|> String.trim()
|> parse_map.()
|> Regions.get()
|> IO.inspect()
|> sum_price.()
puzzle_input
|> parse_map.()
|> Regions.get()
|> sum_price.()