-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
162 lines (156 loc) · 3.52 KB
/
index.ts
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
import run from 'aocrunner'
import * as util from '../utils/index.js'
type NumberMonkey = { name: string; type: 'number'; number: number }
type OperationMonkey = {
name: string
type: 'operation'
operation: '+' | '*' | '/' | '-' | '='
a: string
b: string
number: false | number
}
type AnyMonkey = NumberMonkey | OperationMonkey
const parseInput = (rawInput: string): AnyMonkey[] =>
rawInput
.trim()
.split('\n')
.map((line) => {
const name = line.match(/^([a-z]+):/)![1]
const numberMatch = line.match(/: (\d+)$/)
if (numberMatch) {
return { name, type: 'number', number: +numberMatch[1] }
}
const [a, operation, b] = line.split(':')[1].trim().split(' ')
return {
name,
type: 'operation',
operation,
a,
b,
number: false,
} as OperationMonkey
})
const solveRoot = (monkeyMap: Map<string, AnyMonkey>) => {
const rootMonkey = monkeyMap.get('root') as OperationMonkey
while (true) {
for (const [, monkey] of monkeyMap) {
if (monkey.type === 'number') continue
if (monkey.number !== false) continue
const a = monkeyMap.get(monkey.a)!
if (a.number === false) continue
const b = monkeyMap.get(monkey.b)!
if (b.number === false) continue
switch (monkey.operation) {
case '+':
monkey.number = a.number + b.number
break
case '*':
monkey.number = a.number * b.number
break
case '-':
monkey.number = a.number - b.number
break
case '/':
// Fail on non-integer division
if (a.number % b.number !== 0) return rootMonkey
monkey.number = a.number / b.number
break
case '=':
monkey.number = a.number === b.number ? 1 : 0
break
}
}
if (rootMonkey.number !== false) return rootMonkey
}
}
const part1 = (rawInput: string) => {
const input = parseInput(rawInput)
const monkeyMap: Map<string, AnyMonkey> = new Map(
input.map((m) => [m.name, m])
)
return solveRoot(monkeyMap).number.toString()
}
const part2 = (rawInput: string) => {
const input = parseInput(rawInput)
let equalDiff = 0
let hStep = 1
let lastGoodH = 0
for (let h = 0; h < Number.MAX_SAFE_INTEGER; h += hStep) {
const monkeyMap: Map<string, AnyMonkey> = new Map(
input.map((m) => [m.name, { ...m }])
)
monkeyMap.get('humn')!.number = h
const rootMonkey = monkeyMap.get('root') as OperationMonkey
rootMonkey.operation = '='
const rootA = monkeyMap.get(rootMonkey.a)!
const rootB = monkeyMap.get(rootMonkey.b)!
solveRoot(monkeyMap)
if (rootMonkey.number === 1) {
return h.toString()
} else if (rootMonkey.number === 0) {
const prevEqualDiff = equalDiff
equalDiff = (rootA.number as number) - (rootB.number as number)
if (prevEqualDiff !== 0) {
if (util.sign(equalDiff) !== util.sign(prevEqualDiff)) {
h = lastGoodH
hStep = Math.max(1, hStep / 100)
equalDiff = 0
} else {
lastGoodH = h
hStep = Math.min(1e12, hStep * 10)
}
}
}
}
throw 'ran out of numbers to try'
}
run({
part1: {
tests: [
{
input: `root: pppw + sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: 5
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32`,
expected: '152',
},
],
solution: part1,
},
part2: {
tests: [
{
input: `root: pppw + sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: 5
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32`,
expected: '301',
},
],
solution: part2,
},
trimTestInputs: true,
// onlyTests: true,
})