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

AoC 2023 Day 6

2023/day6.livemd

AoC 2023 Day 6

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

Input

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "6", System.fetch_env!("LB_AOC_COOKIE_SECRET"))
[times, distances] = String.split(puzzle_input, "\n")

[_ | str_times] = String.split(times)
[_ | str_distances] = String.split(distances)

times = Enum.map(str_times, &String.to_integer/1)
distances = Enum.map(str_distances, &String.to_integer/1)

races = Enum.zip(times, distances)

Part 1

The problem can be reduced to a quadratic inequality: $$ \begin{cases} distance(x) = -x^2 + tx \ distance(x) > m \end{cases} $$

We don’t care about all the numbers, just the range higher than the minimal distance m. We can calculate the intersection points with m using the quadratic formula:

$$ x = \frac{-t \pm \sqrt{t^2-4m}}{-2} $$

In some cases, the intersection lands exactly so that $distance(x) = m$. Since we need to complete $distance \gt m$, we special case that to return $x + 1$.

defmodule Race do
  def distance(h, t) do
    -(h * h) + t * h
  end

  def calculate(:low, t, m) do
    h = (-t + :math.sqrt(t * t - 4 * m)) / -2

    case round(distance(h, t)) do
      ^m -> h + 1
      _ -> h
    end
  end

  def calculate(:high, t, m) do
    h = (-t - :math.sqrt(t * t - 4 * m)) / -2

    case round(distance(h, t)) do
      ^m -> h - 1
      _ -> h
    end
  end

  def range({t, m}) do
    low = calculate(:low, t, m) |> :math.floor()
    high = calculate(:high, t, m) |> :math.ceil()
    {low, high}
  end

  def option_count(race) do
    {low, high} = range(race)
    round(high - low + 1)
  end
end
races
|> Enum.map(&Race.option_count/1)
|> Enum.product()

Part 2

race = {
  str_times |> Enum.join() |> String.to_integer(),
  str_distances |> Enum.join() |> String.to_integer()
}

Race.option_count(race)