Skip to main content

aoc/year2022/
day21.rs

1//! # Monkey Math
2//!
3//! The Monkeys form a [binary tree](https://en.wikipedia.org/wiki/Binary_tree). We first
4//! compute the result by recursively following the structure all the way to the leaves.
5//! We also find the `humn` node and all its parents the same way, marking them as "unknown".
6//!
7//! For part two we know that the value on the left and the right of the root must be equal.
8//! Following the tree down the path previously marked "unknown" we recursively solve
9//! equations until we reach the `humn` node.
10//!
11//! For example, say the root's children are `a` and `b`:
12//!
13//! ```none
14//! yell[a] = 6
15//! unknown[a] = false
16//! yell[b] = 5
17//! unknown[b] = true
18//! ```
19//!
20//! So this implies `b` is a parent of `humn` and must equal `6` to pass (the current value is
21//! irrelevant). We then recursively look at the children of `b`:
22//!
23//! ```none
24//! yell[c] = 4
25//! unknown[a] = true
26//! operation = "+"
27//! yell[d] = 4
28//! unknown[b] = false
29//! ```
30//!
31//! We know that `c + d` must equal 6 so this implies `c = 2`. We then recursively look at the
32//! children of `c`
33//!
34//! ```none
35//! yell[humn] = 123
36//! unknown[a] = true
37//! ```
38//!
39//! Once we finally reach the `humn` node the value that we currently have `2` is the answer.
40use crate::util::hash::*;
41use crate::util::parse::*;
42
43#[derive(Clone, Copy)]
44enum Operation {
45    Add,
46    Sub,
47    Mul,
48    Div,
49}
50
51enum Monkey {
52    Number(i64),
53    Result(usize, Operation, usize),
54}
55
56impl Monkey {
57    fn parse(str: &str, indices: &FastMap<&str, usize>) -> Self {
58        if str.len() < 11 {
59            Self::Number(str.signed())
60        } else {
61            let left = indices[&str[0..4]];
62            let right = indices[&str[7..11]];
63            let operation = match str.as_bytes()[5] {
64                b'+' => Operation::Add,
65                b'-' => Operation::Sub,
66                b'*' => Operation::Mul,
67                b'/' => Operation::Div,
68                _ => unreachable!(),
69            };
70            Self::Result(left, operation, right)
71        }
72    }
73}
74
75pub struct Input {
76    root: usize,
77    monkeys: Vec<Monkey>,
78    yell: Vec<i64>,
79    unknown: Vec<bool>,
80}
81
82pub fn parse(input: &str) -> Input {
83    let lines: Vec<_> = input.lines().collect();
84
85    // Assign each monkey an index on a first come first served basis.
86    let indices = lines.iter().enumerate().map(|(index, line)| (&line[0..4], index)).collect();
87
88    let monkeys = lines.iter().map(|line| Monkey::parse(&line[6..], &indices)).collect();
89
90    // We only need the specific indices of the root and human.
91    let root = indices["root"];
92    let humn = indices["humn"];
93    let mut input =
94        Input { root, monkeys, yell: vec![0; lines.len()], unknown: vec![false; lines.len()] };
95
96    compute(&mut input, root);
97    find(&mut input, humn, root);
98    input
99}
100
101pub fn part1(input: &Input) -> i64 {
102    input.yell[input.root]
103}
104
105pub fn part2(input: &Input) -> i64 {
106    inverse(input, input.root, -1)
107}
108
109/// Recursively compute the total following the tree structure all the way to the leaves.
110fn compute(input: &mut Input, index: usize) -> i64 {
111    let result = match input.monkeys[index] {
112        Monkey::Number(n) => n,
113        Monkey::Result(left, operation, right) => {
114            let l = compute(input, left);
115            let r = compute(input, right);
116            match operation {
117                Operation::Add => l + r,
118                Operation::Sub => l - r,
119                Operation::Mul => l * r,
120                Operation::Div => l / r,
121            }
122        }
123    };
124    // Cache the computed value for use in part two.
125    input.yell[index] = result;
126    result
127}
128
129/// Recursively find the humn node then mark it and all its parents all the way to the
130/// root as "unknown".
131fn find(input: &mut Input, humn: usize, index: usize) -> bool {
132    let result = match input.monkeys[index] {
133        Monkey::Number(_) => humn == index,
134        Monkey::Result(left, _, right) => find(input, humn, left) || find(input, humn, right),
135    };
136    input.unknown[index] = result;
137    result
138}
139
140/// Recursively finds the value of the expression on the "unknown" side so that it equals the
141/// known side.
142fn inverse(input: &Input, index: usize, value: i64) -> i64 {
143    let Input { root, yell, unknown, monkeys } = input;
144
145    match monkeys[index] {
146        // The only leaf node we'll actually ever reach is the "humn" node so the value at this
147        // point is the answer.
148        Monkey::Number(_) => value,
149        // If we're the root then the left and right side must be equal.
150        Monkey::Result(left, _, right) if index == *root => {
151            if unknown[left] {
152                inverse(input, left, yell[right])
153            } else {
154                inverse(input, right, yell[left])
155            }
156        }
157        // Addition and multiplication are commutative, but subtraction and division are not,
158        // so we have to handle unknowns on the right and left differently.
159        Monkey::Result(left, operation, right) => {
160            if unknown[left] {
161                let value = match operation {
162                    Operation::Add => value - yell[right],
163                    Operation::Sub => value + yell[right],
164                    Operation::Mul => value / yell[right],
165                    Operation::Div => value * yell[right],
166                };
167                inverse(input, left, value)
168            } else {
169                let value = match operation {
170                    Operation::Add => value - yell[left],
171                    Operation::Sub => yell[left] - value,
172                    Operation::Mul => value / yell[left],
173                    Operation::Div => yell[left] / value,
174                };
175                inverse(input, right, value)
176            }
177        }
178    }
179}