import { readFileSync } from 'node:fs'; const input = readFileSync('input', 'utf-8'); class Monkey { constructor(monkeys, name) { this.monkeys = monkeys; this.name = name; this.value = undefined; this.formulas = []; this.aliases = []; } addFormula(left, operator, right) { this.formulas.push([left, operator, right]); return this; } removeFormulasInvolving(name) { this.formulas = this.formulas.filter( ([left, _, right]) => left !== name && right !== name ); return this; } addAlias(name) { this.aliases.push(name); return this; } setValue(value) { this.value = value; return this; } solve(path = []) { if (this.value !== undefined) { return this.value; } for (const name of this.aliases) { if (path.includes(name)) { continue; } const value = this.monkeys[name].solve([...path, this.name]); if (value !== 'UNKNOWN') { return value; } } for (const [left, operator, right] of this.formulas) { if (path.includes(left) || path.includes(right)) { continue; } const leftValue = this.monkeys[left].solve([...path, this.name]); const rightValue = this.monkeys[right].solve([...path, this.name]); if (leftValue === 'UNKNOWN' || rightValue === 'UNKNOWN') { continue; } switch (operator) { case '*': return leftValue * rightValue; case '/': return leftValue / rightValue; case '+': return leftValue + rightValue; case '-': return leftValue - rightValue; } } return 'UNKNOWN'; } } const monkeys = {}; function getMonkey(name) { if (!monkeys.hasOwnProperty(name)) { monkeys[name] = new Monkey(monkeys, name) } return monkeys[name]; } input .split('\n') .filter(line => !!line.trim()) .forEach(line => { const match = line.match(/^(\w+):\s+(.+?)\s*$/); if (!match) { throw new Error(`Invalid monkey: ${line}`); } const name = match[1]; const job = match[2]; if (/^\d+$/.test(job)) { getMonkey(name).setValue(parseInt(job)); } else { const match = job.match(/^(\w+)\s*([*\/+\-=])\s*(\w+)$/); if (!match) { throw new Error(`Invalid formula: ${formula}`); } const left = match[1]; const operator = match[2]; const right = match[3]; switch (operator) { case '*': getMonkey(name).addFormula(left, '*', right); getMonkey(left).addFormula(name, '/', right); getMonkey(right).addFormula(name, '/', left); break; case '/': getMonkey(name).addFormula(left, '/', right); getMonkey(left).addFormula(name, '*', right); getMonkey(right).addFormula(left, '/', name); break; case '-': getMonkey(name).addFormula(left, '-', right); getMonkey(left).addFormula(name, '+', right); getMonkey(right).addFormula(left, '-', name); break; case '+': getMonkey(name).addFormula(left, '+', right); getMonkey(left).addFormula(name, '-', right); getMonkey(right).addFormula(name, '-', left); break; default: throw new Error(`Invalid operator: ${operator}`); } } }); const rootYells = monkeys['root'].solve(); console.log('The root monkey will yell:', rootYells); for (const monkey of Object.values(monkeys)) { monkey.removeFormulasInvolving('root'); } const {0: left, 2: right} = monkeys['root'].formulas[0]; monkeys[left].addAlias(right); monkeys[right].addAlias(left); const humnYells = monkeys['humn'].setValue(undefined).solve(); console.log('To pass roots equality test, yell:', humnYells);