Add solution for 2022, day 21, part two

This commit is contained in:
Tobias Marschner 2024-09-21 17:54:30 +02:00
parent ed9e9a113d
commit da2bde0fdf
2 changed files with 247 additions and 0 deletions

View File

@ -0,0 +1,11 @@
[package]
name = "day21-part2"
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"] }

View File

@ -0,0 +1,236 @@
use itertools::Itertools;
use std::collections::HashMap;
use id_arena::{Arena, Id};
type MonkeyNumber = i64;
type NodeId = Id<Node>;
#[derive(Debug, Clone)]
enum Node {
Uninitialized,
Unit(MonkeyNumber),
Add(NodeId, NodeId),
Sub(NodeId, NodeId),
Mul(NodeId, NodeId),
Div(NodeId, NodeId),
Human { multiplier: f64, addend: f64 },
}
/// Reduces the expression tree to a minimal representation containing the human value, a.k.a. `x`.
///
/// This modifies the tree. After a reduce operation, each Subtree reduces either
/// into a Unit-type or a Human-type. Crucially, each subtree becomes a leaf.
fn reduce_tree(tree: &mut Arena<Node>, node: NodeId) {
match tree[node].clone() {
// Uninitialized Nodes are illegal at this point.
Node::Uninitialized => panic!("encountered uninitialized Node during evaluation"),
// There is nothing to reduce if this node is already a Unit or Human.
Node::Unit(..) | Node::Human { .. } => (),
Node::Add(lhs, rhs) | Node::Sub(lhs, rhs) | Node::Mul(lhs, rhs) | Node::Div(lhs, rhs) => {
// First, reduce the subtrees.
reduce_tree(tree, lhs);
reduce_tree(tree, rhs);
// Next, merge them depending on subtypes.
match (tree[node].clone(), tree[lhs].clone(), tree[rhs].clone()) {
(Node::Add(..), Node::Unit(lv), Node::Unit(rv)) => tree[node] = Node::Unit(lv + rv),
(Node::Sub(..), Node::Unit(lv), Node::Unit(rv)) => tree[node] = Node::Unit(lv - rv),
(Node::Mul(..), Node::Unit(lv), Node::Unit(rv)) => tree[node] = Node::Unit(lv * rv),
(Node::Div(..), Node::Unit(lv), Node::Unit(rv)) => tree[node] = Node::Unit(lv / rv),
(Node::Add(..), Node::Human { multiplier, addend }, Node::Unit(rv)) => {
tree[node] = Node::Human {
multiplier,
addend: addend + rv as f64,
};
}
(Node::Sub(..), Node::Human { multiplier, addend }, Node::Unit(rv)) => {
tree[node] = Node::Human {
multiplier,
addend: addend - rv as f64,
};
}
(Node::Mul(..), Node::Human { multiplier, addend }, Node::Unit(rv)) => {
tree[node] = Node::Human {
multiplier: multiplier * rv as f64,
addend: addend * rv as f64,
};
}
(Node::Div(..), Node::Human { multiplier, addend }, Node::Unit(rv)) => {
tree[node] = Node::Human {
multiplier: multiplier / rv as f64,
addend: addend / rv as f64,
};
}
(Node::Add(..), Node::Unit(lv), Node::Human { multiplier, addend }) => {
tree[node] = Node::Human {
multiplier,
addend: lv as f64 + addend,
};
}
(Node::Sub(..), Node::Unit(lv), Node::Human { multiplier, addend }) => {
tree[node] = Node::Human {
multiplier: - multiplier,
addend: lv as f64 - addend,
};
}
(Node::Mul(..), Node::Unit(lv), Node::Human { multiplier, addend }) => {
tree[node] = Node::Human {
multiplier: lv as f64 * multiplier,
addend: lv as f64 * addend,
};
}
(Node::Div(..), Node::Unit(lv), Node::Human { multiplier, addend }) => {
tree[node] = Node::Human {
multiplier: lv as f64 / multiplier,
addend: lv as f64 / addend,
};
}
_ => panic!("illegal tree pattern encountered - cannot reduce")
}
}
}
}
fn print_tree(tree: &Arena<Node>, node: NodeId) {
match tree[node] {
Node::Unit(value) => print!("{value}"),
Node::Human { multiplier, addend } => print!("[x * {multiplier} + {addend}]"),
Node::Add(lhs, rhs) => {
print!("(");
print_tree(tree, lhs);
print!(" + ");
print_tree(tree, rhs);
print!(")");
}
Node::Sub(lhs, rhs) => {
print!("(");
print_tree(tree, lhs);
print!(" - ");
print_tree(tree, rhs);
print!(")");
}
Node::Mul(lhs, rhs) => {
print!("(");
print_tree(tree, lhs);
print!(" * ");
print_tree(tree, rhs);
print!(")");
}
Node::Div(lhs, rhs) => {
print!("(");
print_tree(tree, lhs);
print!(" / ");
print_tree(tree, rhs);
print!(")");
}
Node::Uninitialized => panic!("encountered uninitialized Node during evaluation"),
}
}
fn main() {
// Use command line arguments to specify the input filename.
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
panic!("Usage: ./main <input-file>\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::<Node>::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 mut 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::<MonkeyNumber>().ok();
// If this is the 'root' node, override the operation with Sub.
// This is basically akin to an equality check,
// returning 0 if both numbers are equal, and non-zero otherwise.
if node_id == b"root" {
op_char = Some('-');
}
// If this is the human node, override the type and move on to the next.
if node_id == b"humn" {
nodes[node] = Node::Human {
multiplier: 1.0,
addend: 0.0,
};
continue;
}
// 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"),
};
}
// Locate the 'humn' and 'root' nodes.
let root_node = identifiers[b"root"];
print_tree(&nodes, root_node);
println!();
// Now reduce the tree and see what happens.
reduce_tree(&mut nodes, root_node);
print_tree(&nodes, root_node);
println!();
// We assume the tree is now one Human-node. Let's solve it for 0.
let x = if let Node::Human { multiplier, addend } = nodes[root_node] {
- addend / multiplier
} else {
panic!("tree reduction didn't yield human node")
};
println!("The x you're looking for is {}.", x);
}