diff --git a/2022/day21-part1/Cargo.toml b/2022/day21-part1/Cargo.toml new file mode 100644 index 0000000..24b5e4a --- /dev/null +++ b/2022/day21-part1/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day21-part1" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1" +itertools = "0" +id-arena = { version = "2", features = ["rayon"] } diff --git a/2022/day21-part1/src/main.rs b/2022/day21-part1/src/main.rs new file mode 100644 index 0000000..bf2ef76 --- /dev/null +++ b/2022/day21-part1/src/main.rs @@ -0,0 +1,107 @@ +use itertools::Itertools; +use std::collections::HashMap; + +use id_arena::{Arena, Id}; + +type MonkeyNumber = i64; +type NodeId = Id; + +#[derive(Debug)] +enum Node { + Uninitialized, + Unit(MonkeyNumber), + Add(NodeId, NodeId), + Sub(NodeId, NodeId), + Mul(NodeId, NodeId), + Div(NodeId, NodeId), +} + +impl Node { + /// Evaluates the expression tree with the given Node as root node and returns the calculated result. + /// + /// Uses recursion under-the-hood to implement the evaluation. + /// The recursive strategy is essentially a depth-first-search algorithm. + fn evaluate(&self, nodes: &Arena) -> MonkeyNumber { + match self { + Node::Unit(value) => *value, + Node::Add(lhs, rhs) => nodes[*lhs].evaluate(nodes) + nodes[*rhs].evaluate(nodes), + Node::Sub(lhs, rhs) => nodes[*lhs].evaluate(nodes) - nodes[*rhs].evaluate(nodes), + Node::Mul(lhs, rhs) => nodes[*lhs].evaluate(nodes) * nodes[*rhs].evaluate(nodes), + Node::Div(lhs, rhs) => nodes[*lhs].evaluate(nodes) / nodes[*rhs].evaluate(nodes), + Node::Uninitialized => panic!("encountered uninitialized Node during evaluation"), + } + } +} + +fn main() { + // Use command line arguments to specify the input filename. + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + panic!("Usage: ./main \nNo input file provided. Exiting."); + } + + // Next, read the contents of the input file into a string for easier processing. + let input = std::fs::read_to_string(&args[1]).expect("Error opening file"); + + // --- TASK BEGIN --- + + // The complete collection of operation nodes. + let mut nodes = Arena::::with_capacity(input.lines().count()); + // Dictionary mapping the 4-letter identifiers to their respective NodeId. + let mut identifiers = HashMap::<[u8; 4], NodeId>::new(); + + // First, create unitinitialized nodes and populate the identifier-dictionary. + for line in input.lines() { + let node = nodes.alloc(Node::Uninitialized); + identifiers.insert(line[0..4].as_bytes().try_into().unwrap(), node); + } + + // Then, actually populate the nodes. + for line in input.lines() { + // Split into node_id and expression. + let (node_id, expr) = line.split(": ").collect_tuple().unwrap(); + + // Convert node_id from `&str` to `&[u8; 4]`. + let node_id: &[u8; 4] = node_id.as_bytes().try_into().unwrap(); + // Grab the actual NodeId with the identifier. + let node = identifiers[node_id]; + + // Try to see if we can parse + // -> an operator character (+, -, *, /) + // -> a left- and right-hand-side of the operation + // -> an integer for the whole expression + let op_char = expr.chars().nth(5); + let lhs: Option<&[u8; 4]> = expr + .as_bytes() + .get(0..4) + .map(|e| e.try_into().ok()) + .flatten(); + let rhs: Option<&[u8; 4]> = expr + .as_bytes() + .get(7..11) + .map(|e| e.try_into().ok()) + .flatten(); + let monkey_number = expr.parse::().ok(); + + // Now actually assign the node, depending on what we could and could not match. + nodes[node] = match (op_char, lhs, rhs, monkey_number) { + (Some('+'), Some(lhs), Some(rhs), None) => { + Node::Add(identifiers[lhs], identifiers[rhs]) + } + (Some('-'), Some(lhs), Some(rhs), None) => { + Node::Sub(identifiers[lhs], identifiers[rhs]) + } + (Some('*'), Some(lhs), Some(rhs), None) => { + Node::Mul(identifiers[lhs], identifiers[rhs]) + } + (Some('/'), Some(lhs), Some(rhs), None) => { + Node::Div(identifiers[lhs], identifiers[rhs]) + } + (_, _, _, Some(monkey_number)) => Node::Unit(monkey_number), + _ => panic!("could not parse line in input"), + }; + } + + // Evaluate the expression tree and print the result. + println!("Result: {}", nodes[identifiers[b"root"]].evaluate(&nodes)); +}