forked from jiffyclub/pycon-2018-talk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfaster_mode.py
104 lines (86 loc) · 3.25 KB
/
faster_mode.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
import cProfile
import itertools
class Scanner():
def __init__(self, position, cycle_length, shift=True):
'''
Create a scanner and shift its cycle by an offset determined by its
position and frequency. Shifting the cycle means we no longer care about
the position of the scanner in the firewall.
'''
self._cycle = [1] + [0] * (cycle_length-1)
if shift is True:
self.shift_cycle(position)
def __len__(self,):
return len(self._cycle)
def __iter__(self):
for pos in self._cycle:
yield pos
def shift_cycle(self, position):
'''
Shift the cycle relative to its position. Allows for scanners of
similar size or harmonic cycle times to be merged/flattened into a
single scanner. Removes the need to look ahead from the start time to
see if a packet will pass the scanner.
'''
self._cycle = [0] * len(self)
offset = 0 - (position % len(self))
if offset < 0:
offset += len(self)
self._cycle[offset] = 1
def merge(self, scanner):
'''
Merge the passed scanner's cycle into this scanner.
'''
self._cycle = tuple((max(v) for v in zip(scanner, self)))
class Firewall():
def __init__(self, filepath):
'''
From a firewall input file, create a firewall's scanners.
'''
self.scanners = {}
with open(filepath) as f:
for line in f:
scanner_pos, scanner_height = map(int, line.strip().split(': '))
scanner_freq = 2 * (scanner_height - 1)
scanner = Scanner(scanner_pos, scanner_freq)
self.add_scanner(scanner)
self.optimize()
def __iter__(self):
for scanner in self.scanners.values():
yield itertools.cycle(scanner)
def add_scanner(self, scanner):
if len(scanner) in self.scanners:
self.scanners[len(scanner)].merge(scanner)
else:
self.scanners[len(scanner)] = scanner
def optimize(self):
"""
Merge small scanners into larger ones if possible to reduce number of
scanners.
"""
cycle_lengths = sorted(self.scanners.keys())
cycle_max = max(cycle_lengths)
for cycle_lenth in cycle_lengths:
for factor in itertools.count(start=2):
cycle_key = cycle_lenth * factor
if cycle_key > cycle_max or not cycle_key in self.scanners:
break
else:
expanded_scanner = list(self.scanners[cycle_lenth]) * factor
self.scanners[cycle_key].merge(expanded_scanner)
del self.scanners[cycle_lenth]
break
def find_start(firewall):
'''
Unpack scanners from a firewall and simulataneously step through them
to find the minimum start time to get the packet through (aka all
scanners are True.)
'''
for t_start, possible_solution in enumerate(zip(*firewall)):
if 1 in possible_solution:
continue
else:
return t_start
firewall = Firewall(filepath="./day13/input.txt")
cProfile.run('start = find_start(firewall)')
print(f'start at {start}')