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

Advent of Code 2023 - Day 24

2023/24.livemd

Advent of Code 2023 - Day 24

Mix.install([
  {:req, "~> 0.4.0"}
])

Input

opts = [headers: [{"cookie", "session=#{System.fetch_env!("LB_AOC_SESSION")}"}]]
puzzle_input = Req.get!("https://adventofcode.com/2023/day/24/input", opts).body
input =
  puzzle_input
  |> String.split("\n", trim: true)
  |> Enum.map(fn row ->
    row
    |> String.split([", ", " @ "])
    |> Enum.map(&String.to_integer/1)
    |> List.to_tuple()
  end)

Part 1

defmodule Part1 do
  def combinations([]), do: []
  def combinations([head | tail]), do: Enum.map(tail, &{head, &1}) ++ combinations(tail)

  def result({a, b}) do
    if parallel?(a, b) do
      {:parallel, {a, b}, {nil, nil}}
    else
      {x, y} = cross2d(a, b)

      cond do
        cross_outside?({x, y}) -> {:outside, {a, b}, {x, y}}
        cross_past?({x, y}, a, b) -> {:past, {a, b}, {x, y}}
        true -> {:inside, {a, b}, {x, y}}
      end
    end
  end

  defp parallel?({_, _, _, dx1, dy1, _}, {_, _, _, dx2, dy2, _}) do
    dy1 / dx1 == dy2 / dx2
  end

  defp cross2d({x1, y1, _, dx1, dy1, _}, {x2, y2, _, dx2, dy2, _}) do
    s1 = dy1 / dx1
    s2 = dy2 / dx2

    b1 = y1 - s1 * x1
    b2 = y2 - s2 * x2
    x = (b2 - b1) / (s1 - s2)
    y = s1 * x + b1

    {x, y}
  end

  @lower 200_000_000_000_000
  @upper 400_000_000_000_000

  defp cross_outside?({x, y}) do
    Enum.any?([
      @lower > x or x > @upper,
      @lower > y or y > @upper
    ])
  end

  def cross_past?({x, _y}, {x1, _, _, dx1, _, _}, {x2, _, _, dx2, _, _}) do
    Enum.any?([
      (x <= x1 or dx1 <= 0) and (x >= x1 or dx1 >= 0),
      (x <= x2 or dx2 <= 0) and (x >= x2 or dx2 >= 0)
    ])
  end
end

input
|> Part1.combinations()
|> Stream.map(&amp;Part1.result/1)
|> Stream.filter(&amp;(elem(&amp;1, 0) == :inside))
|> Enum.count()

Part 2

Run in Livebook