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

Day 24: Never Tell Me The Odds

day24.livemd

Day 24: Never Tell Me The Odds

Mix.install([
  {:kino, "~> 0.12.0"}
])

Input

input = Kino.Input.textarea("Please, paste your input here:")
defmodule Day24Shared do
  def parse(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n")
    |> Enum.map(fn line ->
      ~r/(\d+),\s+(\d+),\s+(\d+)\s+@\s+(-?\d+),\s+(-?\d+),\s+(-?\d+)/
      |> Regex.run(line, capture: :all_but_first)
      |> Enum.map(&String.to_integer/1)
    end)
  end
end

Day24Shared.parse(input)

Part 1

defmodule Day24Part1 do
  def solve(hailstones, min, max) do
    hailstones =
      Enum.map(hailstones, fn [px, py, _pz, vx, vy, _vz] ->
        {{px, py}, {vx, vy}}
      end)

    for a_stone <- hailstones,
        b_stone <- hailstones,
        a_stone != b_stone,
        uniq: true do
      Enum.sort([a_stone, b_stone])
    end
    |> Enum.map(&amp;cross/1)
    |> Enum.reject(fn cross_data -> cross_data == {nil, nil} end)
    |> Enum.reject(fn {_, past_for_a?, past_for_b?} -> past_for_a? or past_for_b? end)
    |> Enum.reject(fn {{x, y}, _, _} -> x < min or x > max or y < min or y > max end)
    |> Enum.count()
  end

  # Based on formula
  # https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
  defp cross([{{x1, y1}, {vx1, vy1}}, {{x3, y3}, {vx3, vy3}}]) do
    # find a second point of the line using "velocities"
    {x2, y2} = {x1 + vx1, y1 + vy1}
    {x4, y4} = {x3 + vx3, y3 + vy3}

    divisor = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)

    if divisor == 0 do
      # both lines are parallel (or identical when divisor is zero)
      {nil, nil}
    else
      cross_x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / divisor
      cross_y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / divisor

      past_for_a? = in_past?({{x1, y2}, {vx1, vy1}}, {cross_x, cross_y})
      past_for_b? = in_past?({{x3, y3}, {vx3, vy3}}, {cross_x, cross_y})

      {{cross_x, cross_y}, past_for_a?, past_for_b?}
    end
  end

  # Checks where the given crossing coordinate is places around the starting point
  defp in_past?({{x, y}, {vx, vy}}, {cross_x, cross_y}) do
    (cross_x > x and vx < 0) or
      (cross_x < x and vx > 0) or
      (cross_y > y and vy < 0) or
      (cross_y < y and vy > 0)
  end
end

demo_min = 7
demo_max = 27
# min = 200_000_000_000_000
# max = 400_000_000_000_000

input
|> Day24Shared.parse()
|> Day24Part1.solve(demo_min, demo_max)

# 26388 is too high
# 25433 is the right answer!

Part 2

defmodule Day24Part2 do
  def solve(hailstones) do
    hailstones
  end
end

input
|> Day24Shared.parse()
|> Day24Part2.solve()