136 lines
3 KiB
Elixir
136 lines
3 KiB
Elixir
defmodule Day13 do
|
|
defp read_input do
|
|
Path.expand('input', Path.dirname(__ENV__.file))
|
|
|> File.read!()
|
|
end
|
|
|
|
defp test_input do
|
|
"""
|
|
_
|
|
1789,37,47,1889
|
|
"""
|
|
end
|
|
|
|
defp parse_int(val, default) do
|
|
try do
|
|
String.to_integer(val)
|
|
rescue
|
|
ArgumentError -> default
|
|
end
|
|
end
|
|
|
|
defp parse_schedule(text) do
|
|
[line_1, line_2] = String.split(text, "\n", trim: true)
|
|
timestamp = String.to_integer(line_1)
|
|
|
|
bus_ids =
|
|
String.split(line_2, ",")
|
|
|> Enum.map(&parse_int(&1, nil))
|
|
|> Enum.filter(& &1)
|
|
|
|
{timestamp, bus_ids}
|
|
end
|
|
|
|
defp first_multiple_after(timestamp, bus_id) do
|
|
if rem(timestamp, bus_id) == 0 do
|
|
timestamp
|
|
else
|
|
bus_id * (div(timestamp, bus_id) + 1)
|
|
end
|
|
end
|
|
|
|
def part1 do
|
|
{timestamp, bus_ids} = read_input() |> parse_schedule()
|
|
|
|
{bus_id, time} =
|
|
Enum.map(bus_ids, &{&1, first_multiple_after(timestamp, &1)})
|
|
|> Enum.min_by(fn {_, time} -> time end)
|
|
|
|
bus_id * (time - timestamp)
|
|
end
|
|
|
|
defp parse_requirements(text) do
|
|
[_, line_2] = String.split(text, "\n", trim: true)
|
|
|
|
String.split(line_2, ",")
|
|
|> Enum.map(&parse_int(&1, nil))
|
|
|> Enum.with_index()
|
|
|> Enum.filter(fn {offset, _} -> offset end)
|
|
end
|
|
|
|
def part2 do
|
|
{_period, timestamp} =
|
|
read_input()
|
|
|> parse_requirements()
|
|
|> Enum.map(fn {period, offset} -> {period, -offset} end)
|
|
|> Enum.reduce(fn {n1, a1}, {n2, a2} ->
|
|
unless coprime?(n1, n2) do
|
|
raise :wat
|
|
end
|
|
|
|
x = chinese_remainder_theorem(a1, n1, a2, n2)
|
|
n = n1 * n2
|
|
{n, normalize_residue(rem(x, n), n)}
|
|
end)
|
|
|
|
timestamp
|
|
end
|
|
|
|
defp divmod(dividend, divisor) do
|
|
{div(dividend, divisor), rem(dividend, divisor)}
|
|
end
|
|
|
|
def gcd(a, b) do
|
|
{d, _, _, _, _} = extended_euclidean_division(a, b)
|
|
d
|
|
end
|
|
|
|
# Returns {r, m1, d1, m2, d2} where the following properties hold:
|
|
# 1. r == gcd(r1, r2)
|
|
# 2. d1 == abs(r1 / r)
|
|
# 3. d2 == abs(r2 / r)
|
|
# 4. r1 * m1 + r2 * m2 == r
|
|
def extended_euclidean_division(r1, r2) do
|
|
extended_euclidean_division(r1, r2, 1, 0, 0, 1)
|
|
end
|
|
|
|
def extended_euclidean_division(r1, r2, s1, s2, t1, t2) do
|
|
if r2 == 0 do
|
|
{r1, s1, abs(t2), t1, abs(s2)}
|
|
else
|
|
{q, r} = divmod(r1, r2)
|
|
s = s1 - q * s2
|
|
t = t1 - q * t2
|
|
extended_euclidean_division(r2, r, s2, s, t2, t)
|
|
end
|
|
end
|
|
|
|
# m1 * n2 + m2 * n2 = 1
|
|
def bezout_coefficients(n1, n2) do
|
|
{_, m1, _, m2, _} = extended_euclidean_division(n1, n2)
|
|
{m1, m2}
|
|
end
|
|
|
|
# Gives the solution to the following equations:
|
|
# 1. x = a1 (mod n1)
|
|
# 2. x = a2 (mod n2)
|
|
def chinese_remainder_theorem(a1, n1, a2, n2) do
|
|
{m1, m2} = bezout_coefficients(n1, n2)
|
|
a1 * m2 * n2 + a2 * m1 * n1
|
|
end
|
|
|
|
def normalize_residue(r, mod) do
|
|
cond do
|
|
r < 0 -> normalize_residue(r + mod, mod)
|
|
r > mod -> normalize_residue(r - mod, mod)
|
|
true -> r
|
|
end
|
|
end
|
|
|
|
def coprime?(a, b) do
|
|
gcd(a, b) == 1
|
|
end
|
|
end
|
|
|
|
Day13.part1() |> IO.inspect()
|
|
Day13.part2() |> IO.inspect()
|