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)