-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsolution.py
219 lines (181 loc) · 7.28 KB
/
solution.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
"""
Advent of Code 2022: day 11
"""
import operator
import os
import sys
from collections import deque
from functools import reduce
class Monkey:
"""Monkey class, represents a monkey in this problem."""
def __init__(self, op: operator, constant: int | str,
divider: int, modulo: int, items: list[int] = None) -> None:
"""
Initialize a monkey object.,
:param op: The operator used to modify the worry level.
:param constant: The constant used in the worry level modification (or 'old').
:param divider: The constant used to divide the worry level.
:param modulo: The modulo test condition.
:param items: The starting items of the monkey.
"""
self._operator: operator = op
self._constant: int | str = constant
self._divider: int = divider
self._modulo: int = modulo
self._monkey_if_true: Monkey | None = None
self._monkey_if_false: Monkey | None = None
self._items: deque[int] = deque(items if items else [])
self._inspect_count: int = 0
self._modulo_optimizer: int = sys.maxsize
@property
def inspect_count(self) -> int:
"""Get the number of items inspected by this monkey.
:return: The number of items inspected by this monkey.
"""
return self._inspect_count
@property
def modulo(self) -> int:
"""
Get the modulo test condition.
:return: The modulo test condition.
"""
return self._modulo
@property
def modulo_optimizer(self) -> int | None:
"""
Get the modulo optimizer.
:return: The modulo optimizer.
"""
return self._modulo_optimizer
@modulo_optimizer.setter
def modulo_optimizer(self, optimizer: int) -> None:
"""
Set the modulo optimizer.
:param optimizer: The modulo optimizer.
"""
self._modulo_optimizer = optimizer
@property
def monkey_if_true(self) -> 'Monkey | None':
"""
Get the monkey to use if the worry level is a multiple of the modulo.
:return: The monkey, or None if not set.
"""
return self._monkey_if_true
@monkey_if_true.setter
def monkey_if_true(self, monkey: 'Monkey') -> None:
"""
Set the monkey to use if the worry level is a multiple of the modulo.
:param monkey: The monkey to use.
"""
self._monkey_if_true = monkey
@property
def monkey_if_false(self) -> 'Monkey | None':
"""
Get the monkey to use if the worry level is not a multiple of the modulo.
:return: The monkey, or None if not set.
"""
return self._monkey_if_false
@monkey_if_false.setter
def monkey_if_false(self, monkey: 'Monkey') -> None:
"""
Set the monkey to use if the worry level is not a multiple of the modulo.
:param monkey: The monkey to use.
"""
self._monkey_if_false = monkey
def add_item(self, item: int) -> None:
"""
Add an item to the monkey.
:param item: The item to add.
"""
self._items.append(item)
def has_items(self) -> bool:
"""
Check if the monkey has items to inspect.
:return: True if the monkey has items to inspect, False otherwise.
"""
return len(self._items) > 0
def inspect_item(self) -> None:
"""Inspect the first item the monkey holds."""
self._inspect_count += 1
# Modify the worry level
worry_level = self._items.popleft()
constant = self._constant if isinstance(self._constant, int) else worry_level
worry_level = self._operator(worry_level, constant) % self._modulo_optimizer
worry_level = worry_level // self._divider % self._modulo_optimizer
# Worry level meets test condition
if worry_level % self._modulo == 0:
self._monkey_if_true.add_item(worry_level)
return
# Worry level does not meet test condition
self._monkey_if_false.add_item(worry_level)
def parse_monkey(monkey: str, divider: int = 1) -> tuple[Monkey, int, int]:
"""
Parse a monkey from a string.
:param monkey: The string representing the monkey.
:param divider: The divider to use for the worry level.
:return: A tuple containing the monkey, and the indices of monkeys for the test condition.
"""
lines: list[str] = monkey.splitlines()
ops = {'+': operator.add, '*': operator.mul}
# Parse the monkey parameters
starting_items = [int(item) for item in lines[1].split(': ')[-1].split(', ')]
# Parse the operator and constant (number or 'old')
op, constant = lines[2].split('= old ')[-1].split(' ')
op = ops[op]
constant = int(constant) if all(c.isdigit() for c in constant) else constant
# Parse the remaining parameters
modulo = int(lines[3].split(' ')[-1])
if_true = int(lines[4].split(' ')[-1])
if_false = int(lines[5].split(' ')[-1])
# Create the monkey and return
return Monkey(op, constant, divider, modulo, items=starting_items), if_true, if_false
def assign_monkey_relationships(_monkeys: list[tuple[Monkey, int, int]]) -> list[Monkey]:
"""
Assigns the monkey relationships presented in the input.
:param _monkeys: The monkeys to assign relationships to, and their defined relationships.
:return: The list of monkeys.
"""
monkey_list: list[Monkey] = []
# Assign the related monkeys to each monkey
for monkey, if_true_idx, if_false_idx in _monkeys:
monkey.monkey_if_true = _monkeys[if_true_idx][0]
monkey.monkey_if_false = _monkeys[if_false_idx][0]
monkey_list.append(monkey)
return monkey_list
def setup_game(part: int) -> list[Monkey]:
"""
Set up the game for part 1 or part 2.
:param part: The part number of the game.
:return: The list of monkeys in the game.
"""
divider: int = 3 if part == 1 else 1
# Parse the monkeys and assign their relationships
input_file = os.path.join(os.path.dirname(__file__), 'input.txt')
_monkeys: list[str] = open(input_file).read().split('\n\n')
_monkeys: list[tuple[Monkey, int, int]] = [parse_monkey(monkey, divider) for monkey in _monkeys]
_monkeys: list[Monkey] = assign_monkey_relationships(_monkeys)
# Set the modulo optimizer for the monkeys
modulo_optimizer: int = reduce(operator.mul, [monkey.modulo for monkey in _monkeys])
for monkey in _monkeys:
monkey.modulo_optimizer = modulo_optimizer
return _monkeys
def perform_rounds(_monkeys: list[Monkey], rounds: int) -> None:
"""
Perform the rounds of the game.
:param _monkeys: The monkeys to play with.
:param rounds: The number of rounds to play.
"""
# Perform the rounds
for r in range(rounds):
# Each monkey inspects all its items
for monkey in _monkeys:
while monkey.has_items():
monkey.inspect_item()
monkeys: list[Monkey] = setup_game(1)
perform_rounds(monkeys, 20)
top1_monkey, top2_monkey = sorted([monkey.inspect_count for monkey in monkeys])[::-1][:2]
print(f'part1: {top1_monkey * top2_monkey}')
monkeys: list[Monkey] = setup_game(2)
perform_rounds(monkeys, 10000)
top1_monkey, top2_monkey = sorted([monkey.inspect_count for monkey in monkeys])[::-1][:2]
print(f'part2: {top1_monkey * top2_monkey}')