Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2020/day13/day13.ex
2021-12-04 21:20:57 -08:00

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()