diff --git a/2016/d15/ex2/ex2.py b/2016/d15/ex2/ex2.py new file mode 100755 index 0000000..65d13fe --- /dev/null +++ b/2016/d15/ex2/ex2.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +import math +import sys +from typing import NamedTuple + + +class Disc(NamedTuple): + num: int + positions: int + start_offset: int + + @classmethod + def from_str(cls, input: str) -> "Disc": + input = input.removeprefix("Disc #").removesuffix(".") + split_input = input.split() + return Disc( + int(split_input[0]), + int(split_input[2]), + int(split_input[-1]), + ) + + +def solve(input: str) -> int: + def parse(input: str) -> list[Disc]: + return [Disc.from_str(line) for line in input.splitlines()] + + # https://rosettacode.org/wiki/Chinese_remainder_theorem#Python_3.6 + def mul_inv(a: int, b: int) -> int: + b0 = b + x0, x1 = 0, 1 + if b == 1: + return 1 + while a > 1: + q = a // b + a, b = b, a % b + x0, x1 = x1 - q * x0, x0 + if x1 < 0: + x1 += b0 + return x1 + + def chinese_remainder(residue_mapping: dict[int, int]) -> int: + res = 0 + prod = math.prod(residue_mapping) + for n_i, a_i in residue_mapping.items(): + p = prod // n_i + res += a_i * mul_inv(p, n_i) * p + return res % prod + + discs = parse(input) + discs.append(Disc(len(discs) + 1, 11, 0)) + residues = {disc.positions: -(disc.start_offset + disc.num) for disc in discs} + return chinese_remainder(residues) + + +def main() -> None: + input = sys.stdin.read() + print(solve(input)) + + +if __name__ == "__main__": + main()