Add solution for 2022, day 21, part two
This commit is contained in:
parent
ed9e9a113d
commit
da2bde0fdf
11
2022/day21-part2/Cargo.toml
Normal file
11
2022/day21-part2/Cargo.toml
Normal 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"] }
|
||||||
236
2022/day21-part2/src/main.rs
Normal file
236
2022/day21-part2/src/main.rs
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user