-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathday13_shuttle.py
154 lines (119 loc) · 4.09 KB
/
day13_shuttle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from dataclasses import dataclass
import math
from typing import List, NamedTuple
import pytest
# Part 1
class Schedule(NamedTuple):
timestamp: int
buses: List[int]
def parse_bus_schedule(lines):
timestamp, to_process = lines
active_buses = [
int(value) for value in to_process.strip().split(",") if value != "x"
]
return Schedule(int(timestamp), active_buses)
def next_bus(buses, timestamp):
time_to_wait = []
for bus in buses:
num_trips = timestamp // bus
last_bus = num_trips * bus
time_to_wait.append(last_bus + bus - timestamp)
time_to_wait_for_next_bus = min(time_to_wait)
bus_number = buses[time_to_wait.index(time_to_wait_for_next_bus)]
return time_to_wait_for_next_bus * bus_number
def test_find_next_bus():
TEST_INPUT = """939
7,13,x,x,59,x,31,19"""
schedule = parse_bus_schedule(TEST_INPUT.split("\n"))
assert next_bus(schedule.buses, schedule.timestamp) == 295
# Part 2
@dataclass
class Bus:
period: int
offset: int
time: int = 0
@property
def time_without_offset(self):
return self.time - self.offset
def __next__(self):
self.time += self.period
while True:
return self.time
def parse_bus_and_offsets(businfo):
buses = []
for idx, period in enumerate(businfo.split(",")):
if period == "x":
continue
bus = Bus(int(period), idx)
next(bus)
buses.append(bus)
return buses
def find_time_when_sequential(schedule: List[Bus]):
while True:
time_without_offset = [bus.time_without_offset for bus in schedule]
differences = [item == time_without_offset[0] for item in time_without_offset]
if all(differences):
return time_without_offset[0]
min_bus = min(time_without_offset)
idx = time_without_offset.index(min_bus)
bus_to_increment = schedule[idx]
next(bus_to_increment)
# @pytest.mark.parametrize(
# "input_str,timestamp",
# [
# ("7,13,x,x,59,x,31,19", 1068781),
# ("17,x,13,19", 3417),
# ("67,7,59,61", 754018),
# ("67,x,7,59,61", 779210),
# ("67,7,x,59,61", 1261476),
# ("1789,37,47,1889", 1202161486), # runs way too slowly
# ],
# )
# def test_find_time_when_sequential(input_str, timestamp):
# schedule = parse_bus_and_offsets(input_str)
# result = find_time_when_sequential(schedule)
# assert result == timestamp
def find_time_when_sequential_enchanced(schedule: List[Bus]):
# set up problem
# x is congruent to remainder0 (mod prime0)
# x is congruent to remainder1 (mod prime1)
# x is congruent to remainder2 (mod prime2)
# ...
primes = [bus.period for bus in schedule]
remainders = [bus.period - bus.offset for bus in schedule]
x = chinese_remainder_theorem(remainders, primes)
return x
def chinese_remainder_theorem(remainders, primes):
# pairwise co-primes (should probably check)
N = math.prod(primes)
a_s = []
for remainder, prime in zip(remainders, primes):
n_i = N // prime
s_i = pow(n_i, -1, mod=prime)
a_i = remainder * n_i * s_i
a_s.append(a_i)
return sum(a_s) % N
@pytest.mark.parametrize(
"input_str,timestamp",
[
("7,13,x,x,59,x,31,19", 1068781),
("17,x,13,19", 3417),
("67,7,59,61", 754018),
("67,x,7,59,61", 779210),
("67,7,x,59,61", 1261476),
("1789,37,47,1889", 1202161486),
],
)
def test_find_time_when_sequential_enchanced(input_str, timestamp):
# import pdb; pdb.set_trace()
schedule = parse_bus_and_offsets(input_str)
result = find_time_when_sequential_enchanced(schedule)
assert result == timestamp
if __name__ == "__main__":
with open("2020/data/day13_input.txt") as f:
schedule = parse_bus_schedule(f.readlines())
print(f"Part 1 is {next_bus(schedule.buses, schedule.timestamp)}")
with open("2020/data/day13_input.txt") as f:
line = f.readlines()[1]
schedule = parse_bus_and_offsets(line.strip())
print(f"Part 2 is {find_time_when_sequential_enchanced(schedule)}")