-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
225 lines (185 loc) · 4.65 KB
/
main.go
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
220
221
222
223
224
225
package main
import (
"github.com/danvolchek/AdventOfCode/lib"
)
type Monkey struct {
name string
num int
isConst bool
dep1, dep2 string
op byte
}
func parse(line string) Monkey {
nums := lib.Ints(line)
if len(nums) == 1 {
return Monkey{
name: line[0:4],
num: nums[0],
isConst: true,
}
}
lineAfter := line[6:]
return Monkey{
name: line[0:4],
dep1: lineAfter[0:4],
dep2: lineAfter[7:],
op: lineAfter[5],
}
}
type MonkeyOp interface {
// HasVariable returns whether this MonkeyOp eventually contains the variable being solved for.
HasVariable() bool
// Evaluate evaluates the value of this MonkeyOp. It must not have any variables in it.
Evaluate() int
// Reverse computes the value needed for this MonkeyOp such that Evaluate would return target.
Reverse(target int) int
}
// Used to sanity check our answer. nil if we don't have an answer yet.
var sanityCheckAnswer *int
// The human monkey. It is the variable, can only be evaluated if the problem is solved/being checked,
// and reversing it yields the answer.
type humanMonkey struct {
}
func (h humanMonkey) HasVariable() bool {
return true
}
func (h humanMonkey) Evaluate() int {
if sanityCheckAnswer == nil {
panic("can't evaluate human")
}
return *sanityCheckAnswer
}
func (h humanMonkey) Reverse(target int) int {
return target
}
// A constant monkey. It is not the variable, evaluates to its constant value, and can't be reversed.
type constMonkey struct {
name string
constant int
}
func (c constMonkey) HasVariable() bool {
return false
}
func (c constMonkey) Evaluate() int {
return c.constant
}
func (c constMonkey) Reverse(_ int) int {
panic("can't reverse constant")
}
// An operation monkey, which performs a binary operation on two other monkeys.
// It has a variable if either of its sub operations have a variable, and can be evaluated and reversed.
type operationMonkey struct {
left, right MonkeyOp
which byte
}
func (b operationMonkey) HasVariable() bool {
return b.left.HasVariable() || b.right.HasVariable()
}
func (b operationMonkey) Evaluate() int {
left, right := b.left.Evaluate(), b.right.Evaluate()
var result int
switch b.which {
case '+':
result = left + right
case '*':
result = left * right
case '/':
result = left / right
case '-':
result = left - right
case '=':
result = 1
if left != right {
result = 0
}
default:
panic(b.which)
}
return result
}
func (b operationMonkey) Reverse(target int) int {
var val int
var other MonkeyOp
leftVar, rightVar := b.left.HasVariable(), b.right.HasVariable()
if leftVar && rightVar || (!leftVar && !rightVar) {
panic("must be exactly one side constant and one side variable")
}
if leftVar {
other = b.left
val = b.right.Evaluate()
} else {
other = b.right
val = b.left.Evaluate()
}
switch b.which {
case '+':
return other.Reverse(target - val)
case '*':
return other.Reverse(target / val)
case '/':
if other == b.left {
return other.Reverse(val * target)
}
return other.Reverse(val / target)
case '-':
if other == b.left {
return other.Reverse(val + target)
}
return other.Reverse(val - target)
case '=':
return other.Reverse(val)
default:
panic(b.which)
}
}
// createOp creates a MonkeyOp from a Monkey.
func createOp(curr Monkey, monkeyMap map[string]Monkey) MonkeyOp {
switch curr.name {
case "root":
return operationMonkey{
left: createOp(monkeyMap[curr.dep1], monkeyMap),
right: createOp(monkeyMap[curr.dep2], monkeyMap),
which: '=',
}
case "humn":
return humanMonkey{}
default:
if curr.isConst {
return constMonkey{
name: curr.name,
constant: curr.num,
}
}
return &operationMonkey{
left: createOp(monkeyMap[curr.dep1], monkeyMap),
right: createOp(monkeyMap[curr.dep2], monkeyMap),
which: curr.op,
}
}
}
func solve(lines []Monkey) int {
sanityCheckAnswer = nil
// build monkey operation tree
monkeyMap := make(map[string]Monkey)
for _, monkey := range lines {
monkeyMap[monkey.name] = monkey
}
rootMonkey := createOp(monkeyMap["root"], monkeyMap)
// calculate the answer
answer := rootMonkey.Reverse(0)
// sanity check the answer by evaluating with that answer
sanityCheckAnswer = &answer
result := rootMonkey.Evaluate()
if result != 1 {
panic("answer is wrong somehow")
}
return answer
}
func main() {
solver := lib.Solver[[]Monkey, int]{
ParseF: lib.ParseLine(parse),
SolveF: solve,
}
solver.Expect("root: pppw + sjmn\ndbpl: 5\ncczh: sllz + lgvd\nzczc: 2\nptdq: humn - dvpt\ndvpt: 3\nlfqf: 4\nhumn: 5\nljgn: 2\nsjmn: drzm * dbpl\nsllz: 4\npppw: cczh / lfqf\nlgvd: ljgn * ptdq\ndrzm: hmdt - zczc\nhmdt: 32", 301)
solver.Verify(3342154812537)
}