Powered by AppSignal & Oban Pro

Advent of code 2025 - Day 9

day09.livemd

Advent of code 2025 - Day 9

Description

Day 9 - Movie Theater

defmodule Load do
  def input do
    File.read!("#{__DIR__}/inputs/day09.txt")
  end
end
defmodule Day9 do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn row ->
      String.split(row, ",") |> Enum.map(&String.to_integer/1) |> List.to_tuple()
    end)
  end

  def part1(input) do
    coordinates = parse(input)

    for {x1, y1} <- coordinates,
        {x2, y2} <- coordinates,
        reduce: 0 do
      acc -> max(acc, abs(x2 - x1 + 1) * abs(y2 - y1 + 1))
    end
  end

  def part2(input) do
    coordinates = parse(input)
    boundary = build_boundary_segments(coordinates)

    for {x1, y1} <- coordinates,
        {x2, y2} <- coordinates,
        x1 < x2 and y1 != y2,
        rectangle_fits_inside?(x1, x2, min(y1, y2), max(y1, y2), boundary),
        reduce: 0 do
      acc -> max(acc, (x2 - x1 + 1) * (abs(y2 - y1) + 1))
    end
  end

  defp build_boundary_segments(coordinates) do
    coordinates
    |> Enum.chunk_every(2, 1, [hd(coordinates)])
    |> Enum.map(fn [{x1, y1}, {x2, y2}] ->
      if x1 == x2,
        do: {:vertical, x1, Enum.min_max([y1, y2])},
        else: {:horizontal, y1, Enum.min_max([x1, x2])}
    end)
  end

  defp rectangle_fits_inside?(x1, x2, y1, y2, boundary) do
    not Enum.any?(boundary, fn segment ->
      segment_crosses_interior?(segment, x1, x2, y1, y2)
    end)
  end

  defp segment_crosses_interior?({:vertical, x, {seg_y1, seg_y2}}, x1, x2, y1, y2) do
    x > x1 and x < x2 and seg_y2 > y1 and seg_y1 < y2
  end

  defp segment_crosses_interior?({:horizontal, y, {seg_x1, seg_x2}}, x1, x2, y1, y2) do
    y > y1 and y < y2 and seg_x2 > x1 and seg_x1 < x2
  end
end
ExUnit.start(autorun: false)

defmodule Test do
  use ExUnit.Case, async: true

  @input """
  7,1
  11,1
  11,7
  9,7
  9,5
  2,5
  2,3
  7,3
  """

  test "part 1" do
    assert Day9.part1(@input) == 50
  end

  test "part 2" do
    assert Day9.part2(@input) == 24
  end
end

ExUnit.run()
Day9.part1(Load.input())
Day9.part2(Load.input())