-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathaoc201610_generator.py
141 lines (107 loc) · 3.8 KB
/
aoc201610_generator.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
"""AoC 10, 2016: Balance Bots."""
# Standard library imports
import collections
import pathlib
import sys
# Third party imports
import parse
PATTERN_INPUT = parse.compile("value {value:d} goes to bot {bot:d}")
PATTERN_COMPARISON = parse.compile("bot {bot:d} gives low to {low} and high to {high}")
PATTERN_BOT = parse.compile("bot {bot:d}")
PATTERN_OUTPUT = parse.compile("output {output:d}")
def parse_data(puzzle_input):
"""Parse input."""
bots = collections.defaultdict(list)
comparisons = {}
for line in puzzle_input.split("\n"):
bots = parse_input(bots, line)
comparisons = parse_comparison(comparisons, line)
return distribute(bots, comparisons)
def parse_input(bots, line):
"""Parse one line with input instructions.
## Examples:
>>> parse_input({1: [4]}, "value 3 goes to bot 1")
{1: [4, 3]}
>>> parse_input({}, "bot 2 gives low to bot 1 and high to bot 0")
{}
"""
if match := PATTERN_INPUT.parse(line):
return bots | {match["bot"]: bots[match["bot"]] + [match["value"]]}
else:
return bots
def parse_comparison(comparisons, line):
"""Parse one line with comparison instructions.
>>> parse_comparison({}, "value 3 goes to bot 1")
{}
>>> parse_comparison({}, "bot 2 gives low to bot 1 and high to bot 0")
{2: (1, 0)}
>>> parse_comparison({2: (1, 0)}, "bot 1 gives low to output 1 and high to bot 0")
{2: (1, 0), 1: (1001, 0)}
"""
if match := PATTERN_COMPARISON.parse(line):
return comparisons | {
match["bot"]: (
parse_bot_or_output(match["low"]),
parse_bot_or_output(match["high"]),
)
}
else:
return comparisons
def parse_bot_or_output(bot_or_output):
"""Parse a bot or an output specification.
## Examples:
>>> parse_bot_or_output("bot 4")
4
>>> parse_bot_or_output("output 7")
1007
"""
if match := PATTERN_BOT.parse(bot_or_output):
return match["bot"]
elif match := PATTERN_OUTPUT.parse(bot_or_output):
return match["output"] + 1000
def part1(data, chips=(17, 61)):
"""Solve part 1."""
for bot, low, high in data:
if (low, high) == chips:
return bot
def part2(data):
"""Solve part 2."""
outputs = (0, 0, 0)
for outputs in data:
pass
return outputs[0] * outputs[1] * outputs[2]
def distribute(bots, comparisons):
"""Distribute chips according to instructions.
Yield comparisons so they can be monitored from the outside.
## Example:
>>> list(distribute({2: [9, 4], 1: [6]}, {2: (1, 0), 1: (1001, 0), 0: (1002, 1000)}))
[(2, 4, 9), (1, 4, 6), (0, 6, 9), {1: 4, 2: 6, 0: 9}]
"""
outputs = {}
while comparisons:
for bot, chips in bots.items():
if len(chips) < 2:
continue
low, high = min(chips), max(chips)
yield bot, low, high
to_low, to_high = comparisons[bot]
bots = {b: c for b, c in bots.items() if b != bot} | {
b: bots.get(b, []) + [c]
for b, c in [(to_low, low), (to_high, high)]
if b < 1000
}
outputs = outputs | {
o: c for o, c in [(to_low, low), (to_high, high)] if o >= 1000
}
comparisons = {b: c for b, c in comparisons.items() if b != bot}
yield {output - 1000: value for output, value in outputs.items()}
def solve(puzzle_input):
"""Solve the puzzle for the given input."""
data = parse_data(puzzle_input)
yield part1(data)
yield part2(data)
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().rstrip())
print("\n".join(str(solution) for solution in solutions))