-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy path__init__.py
146 lines (115 loc) · 4.3 KB
/
__init__.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
from __future__ import annotations
from typing import *
from aocpy import BaseChallenge
from enum import Enum
import re
class Operator(Enum):
ADD = "+"
SUB = "-"
DIV = "/"
MULT = "*"
def inverse(self) -> Operator:
if self.value == self.ADD.value:
return self.SUB
elif self.value == self.SUB.value:
return self.ADD
elif self.value == self.MULT.value:
return self.DIV
else:
return self.MULT
def calc(self, a_val, b_val) -> float:
res = 0
if self.value == self.ADD.value:
res = a_val + b_val
elif self.value == self.SUB.value:
res = a_val - b_val
elif self.value == self.MULT.value:
res = a_val * b_val
elif self.value == self.DIV.value:
res = a_val / b_val
return res
Monkey = Union[int, Tuple[str, Operator, str]]
Monkies = Dict[str, Monkey]
number_re = re.compile(r"([a-z]{4}): (\d+)")
operation_re = re.compile(r"([a-z]{4}): ([a-z]{4}) ([+\-/*]) ([a-z]{4})")
def parse(instr: str) -> Monkies:
res: Dict[str, Monkey] = {}
for line in instr.strip().splitlines():
if x := number_re.match(line):
name, num_str = x.groups()
num = int(num_str)
res[name] = num
elif x := operation_re.match(line):
name, a, op_str, b = x.groups()
res[name] = (a, Operator(op_str), b)
return res
def evaluate_monkey(monkey: Monkey, monkies: Monkies) -> float:
if type(monkey) == int:
return monkey
elif type(monkey) == tuple:
(a_name, op, b_name) = monkey
a_val = evaluate_monkey(monkies[a_name], monkies)
b_val = evaluate_monkey(monkies[b_name], monkies)
return op.calc(a_val, b_val)
raise ValueError(f"impossible type {type(monkey)}")
def does_branch_contain(target: str, monkey: Monkey, monkies: Monkies) -> bool:
if type(monkey) == int:
return False
elif type(monkey) == tuple:
(a_name, _, b_name) = monkey
if a_name == target or b_name == target:
return True
if does_branch_contain(target, monkies[a_name], monkies):
return True
return does_branch_contain(target, monkies[b_name], monkies)
raise ValueError(f"impossible type {type(monkey)}")
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
inp = parse(instr)
return int(evaluate_monkey(inp["root"], inp))
@staticmethod
def two(instr: str) -> int:
inp = parse(instr)
root = inp["root"]
assert type(root) == tuple
(a_name, _, b_name) = root
if does_branch_contain("humn", inp[a_name], inp):
branch_with_human = inp[a_name]
target_value = evaluate_monkey(inp[b_name], inp)
elif does_branch_contain("humn", inp[b_name], inp):
branch_with_human = inp[b_name]
target_value = evaluate_monkey(inp[a_name], inp)
else:
raise ValueError("neither root branch contains humn")
acc = target_value
cursor = branch_with_human
while True:
(a_name, op, b_name) = cursor
if a_name == "humn" or does_branch_contain("humn", inp[a_name], inp):
x = evaluate_monkey(inp[b_name], inp)
is_first_arg = False
cursor = inp[a_name]
elif b_name == "humn" or does_branch_contain("humn", inp[b_name], inp):
x = evaluate_monkey(inp[a_name], inp)
is_first_arg = True
cursor = inp[b_name]
else:
raise ValueError("neither branch contains humn")
if op == Operator.ADD or op == Operator.MULT:
acc = op.inverse().calc(acc, x)
elif op == Operator.SUB:
if is_first_arg:
acc = op.calc(-acc, -x)
else:
acc = op.inverse().calc(acc, x)
elif op == Operator.DIV:
if is_first_arg:
acc = op.calc(1 / acc, 1 / x)
else:
acc = op.inverse().calc(acc, x)
else:
raise ValueError(f"invalid operator {op}")
if a_name == "humn" or b_name == "humn":
break
return int(acc)