diff --git a/day13.py b/day13.py index ce8bd22..216eb27 100644 --- a/day13.py +++ b/day13.py @@ -20,17 +20,27 @@ def part1(test_mode=False): def part2(test_mode=False): my_input = aoclib.getInputAsArraySplit(day=DAY, split_char=",", test=test_mode) - bus_ids = {-i: int(v) for i, v in enumerate(my_input[1]) if v != 'x'} - lowest_common_multiple = math.lcm(*bus_ids.values()) - base_multipliers = [lowest_common_multiple // interval for interval in bus_ids.values()] - modular_multiplicative_inverses = [ - pow(base_multiplier, -1, interval) - for base_multiplier, interval - in zip(base_multipliers, bus_ids.values()) - ] + bus_ids = {i: int(v) for i, v in enumerate(my_input[1]) if v != 'x'} + # utilizing the Chinese Remainder Theorem we are searching for x = i (mod bus_ids[i]) for all i + # watch https://www.youtube.com/watch?v=ru7mWZJlRQg for an easy explanation + + # finding the "left" part of each (mod x) part + base_multiplier = {i: math.prod(bus_ids.values()) // v for i, v in bus_ids.items()} + + # finding the "right" part of each (mod x) part utilizing the Extended Euclidean Algorithm + # s. Python documentation on pow(x, -1, y) + ext_multiplier = {i: pow(base_multiplier[i], -1, bus_ids[i]) for i in bus_ids} + + # sum all multiplications together and add our offset + # EEA gives us base_multiplier[i] * x == 1(one!) (mod bus_ids[i]) + # but we need base_multiplier[i] * x == i (mod bus_ids[i]) + answer = sum(i * base_multiplier[i] * ext_multiplier[i] for i in bus_ids) + + # and shrink it down + # for the -answer see pythons behaviour when calculating the mod of negative numbers with positive divisor + # also: http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html + lowest_common_multiple = math.lcm(*bus_ids.values()) + answer = -answer % lowest_common_multiple + + return answer - return sum( - offset * multiplier * mmi - for offset, multiplier, mmi - in zip(bus_ids.keys(), base_multipliers, modular_multiplicative_inverses) - ) % lowest_common_multiple