Solve day21
This commit is contained in:
parent
be9627ae47
commit
7353989dd2
2969
day21/input
Normal file
2969
day21/input
Normal file
File diff suppressed because it is too large
Load diff
15
day21/input.test
Normal file
15
day21/input.test
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
root: pppw + sjmn
|
||||||
|
dbpl: 5
|
||||||
|
cczh: sllz + lgvd
|
||||||
|
zczc: 2
|
||||||
|
ptdq: humn - dvpt
|
||||||
|
dvpt: 3
|
||||||
|
lfqf: 4
|
||||||
|
humn: 5
|
||||||
|
ljgn: 2
|
||||||
|
sjmn: drzm * dbpl
|
||||||
|
sllz: 4
|
||||||
|
pppw: cczh / lfqf
|
||||||
|
lgvd: ljgn * ptdq
|
||||||
|
drzm: hmdt - zczc
|
||||||
|
hmdt: 32
|
|
@ -1,3 +1,287 @@
|
||||||
fn main() {
|
use std::{
|
||||||
println!("Hello, world!");
|
collections::HashMap,
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Monkey = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
enum SolvePuzzle {
|
||||||
|
First,
|
||||||
|
#[default]
|
||||||
|
Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TreeReduce {
|
||||||
|
type Output: Clone;
|
||||||
|
const VAR: Self::Output;
|
||||||
|
fn reduce(op: Op, o1: Self::Output, o2: Self::Output) -> Self::Output;
|
||||||
|
fn reduce_equals(o1: Self::Output, o2: Self::Output) -> Self::Output;
|
||||||
|
fn wrap_const(c: u64) -> Self::Output;
|
||||||
|
|
||||||
|
fn reduce_tree(tree: &mut HashMap<Monkey, Operation>, root: &Monkey) -> Self::Output {
|
||||||
|
match tree.remove(root).expect("Monkey in tree") {
|
||||||
|
Operation::Simple(op, m1, m2) => {
|
||||||
|
let output1 = Self::reduce_tree(tree, &m1);
|
||||||
|
let output2 = Self::reduce_tree(tree, &m2);
|
||||||
|
Self::reduce(op, output1, output2)
|
||||||
|
}
|
||||||
|
Operation::Const(c) => Self::wrap_const(c),
|
||||||
|
Operation::Equals(m1, m2) => {
|
||||||
|
let output1 = Self::reduce_tree(tree, &m1);
|
||||||
|
let output2 = Self::reduce_tree(tree, &m2);
|
||||||
|
Self::reduce_equals(output1, output2)
|
||||||
|
}
|
||||||
|
Operation::Var => Self::VAR.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Op {
|
||||||
|
Add,
|
||||||
|
Sub,
|
||||||
|
Mul,
|
||||||
|
Div,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Op {
|
||||||
|
fn apply_to(&self, number1: u64, number2: u64) -> u64 {
|
||||||
|
match self {
|
||||||
|
Op::Add => number1 + number2,
|
||||||
|
Op::Sub => number1 - number2,
|
||||||
|
Op::Mul => number1 * number2,
|
||||||
|
Op::Div => number1 / number2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum Operation {
|
||||||
|
Simple(Op, Monkey, Monkey),
|
||||||
|
Equals(Monkey, Monkey),
|
||||||
|
Const(u64),
|
||||||
|
Var,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
fn into_monkeys(self) -> Option<(Monkey, Monkey)> {
|
||||||
|
match self {
|
||||||
|
Self::Simple(_, m1, m2) | Self::Equals(m1, m2) => Some((m1, m2)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_input<const FIX_MISUNDERSTANDINGS: bool, R: BufRead>(
|
||||||
|
reader: R,
|
||||||
|
) -> HashMap<Monkey, Operation> {
|
||||||
|
reader
|
||||||
|
.lines()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.map(|line| {
|
||||||
|
let (monkey, rest) = line.split_once(": ").unwrap();
|
||||||
|
let monkey = monkey.to_owned();
|
||||||
|
let rest: Operation = rest.parse().unwrap();
|
||||||
|
if FIX_MISUNDERSTANDINGS && monkey == "root" {
|
||||||
|
// Somehow the root monkey is only comparing numbers
|
||||||
|
let (m1, m2) = rest.into_monkeys().expect("Root never has a constant");
|
||||||
|
(monkey, Operation::Equals(m1, m2))
|
||||||
|
} else if FIX_MISUNDERSTANDINGS && monkey == "humn" {
|
||||||
|
// That's us!
|
||||||
|
(monkey, Operation::Var)
|
||||||
|
} else {
|
||||||
|
(monkey, rest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ApplyOps;
|
||||||
|
impl TreeReduce for ApplyOps {
|
||||||
|
type Output = u64;
|
||||||
|
|
||||||
|
const VAR: Self::Output = 0;
|
||||||
|
|
||||||
|
fn reduce(op: Op, o1: Self::Output, o2: Self::Output) -> Self::Output {
|
||||||
|
op.apply_to(o1, o2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce_equals(_o1: Self::Output, _o2: Self::Output) -> Self::Output {
|
||||||
|
panic!("ApplyOps cannot resolve Equals!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_const(c: u64) -> Self::Output {
|
||||||
|
c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_first_puzzle<R: BufRead>(reader: R) -> u64 {
|
||||||
|
let mut tree = parse_input::<false, _>(reader);
|
||||||
|
let root = String::from("root");
|
||||||
|
ApplyOps::reduce_tree(&mut tree, &root)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum ConstOrStack {
|
||||||
|
Constant(u64),
|
||||||
|
Stack(Vec<StackOp>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstOrStack {
|
||||||
|
fn apply_op(self, op: Op, other: Self) -> Self {
|
||||||
|
match (self, other) {
|
||||||
|
(ConstOrStack::Constant(c1), ConstOrStack::Constant(c2)) => {
|
||||||
|
ConstOrStack::Constant(op.apply_to(c1, c2))
|
||||||
|
}
|
||||||
|
(ConstOrStack::Constant(constant), ConstOrStack::Stack(mut stack)) => {
|
||||||
|
stack.push(StackOp {
|
||||||
|
op,
|
||||||
|
constant,
|
||||||
|
apply_to_right: false,
|
||||||
|
});
|
||||||
|
ConstOrStack::Stack(stack)
|
||||||
|
}
|
||||||
|
(ConstOrStack::Stack(mut stack), ConstOrStack::Constant(constant)) => {
|
||||||
|
stack.push(StackOp {
|
||||||
|
op,
|
||||||
|
constant,
|
||||||
|
apply_to_right: true,
|
||||||
|
});
|
||||||
|
ConstOrStack::Stack(stack)
|
||||||
|
}
|
||||||
|
(ConstOrStack::Stack(_), ConstOrStack::Stack(_)) => {
|
||||||
|
unreachable!("Not nice, but not needed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct StackOp {
|
||||||
|
op: Op,
|
||||||
|
constant: u64,
|
||||||
|
apply_to_right: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EqualityHero;
|
||||||
|
impl TreeReduce for EqualityHero {
|
||||||
|
type Output = ConstOrStack;
|
||||||
|
|
||||||
|
const VAR: Self::Output = ConstOrStack::Stack(vec![]);
|
||||||
|
|
||||||
|
fn reduce(op: Op, o1: Self::Output, o2: Self::Output) -> Self::Output {
|
||||||
|
o1.apply_op(op, o2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reduce_equals(o1: Self::Output, o2: Self::Output) -> Self::Output {
|
||||||
|
match (o1, o2) {
|
||||||
|
(ConstOrStack::Constant(constant), ConstOrStack::Stack(stack))
|
||||||
|
| (ConstOrStack::Stack(stack), ConstOrStack::Constant(constant)) => {
|
||||||
|
// This is the only real possibility
|
||||||
|
let mut current_number = constant;
|
||||||
|
for StackOp {
|
||||||
|
op,
|
||||||
|
constant,
|
||||||
|
apply_to_right,
|
||||||
|
} in stack.into_iter().rev()
|
||||||
|
{
|
||||||
|
// Undo the operation
|
||||||
|
current_number = if apply_to_right {
|
||||||
|
match op {
|
||||||
|
Op::Add => current_number - constant,
|
||||||
|
Op::Sub => current_number + constant,
|
||||||
|
Op::Mul => current_number / constant,
|
||||||
|
Op::Div => current_number * constant,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match op {
|
||||||
|
Op::Add => current_number - constant,
|
||||||
|
Op::Sub => constant - current_number,
|
||||||
|
Op::Mul => current_number / constant,
|
||||||
|
Op::Div => constant / current_number,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ConstOrStack::Constant(current_number)
|
||||||
|
}
|
||||||
|
(ConstOrStack::Constant(_), ConstOrStack::Constant(_)) => todo!(),
|
||||||
|
(ConstOrStack::Stack(_), ConstOrStack::Stack(_)) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_const(c: u64) -> Self::Output {
|
||||||
|
ConstOrStack::Constant(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_second_puzzle<R: BufRead>(reader: R) -> u64 {
|
||||||
|
let mut tree = parse_input::<true, _>(reader);
|
||||||
|
let root = String::from("root");
|
||||||
|
if let ConstOrStack::Constant(result) = EqualityHero::reduce_tree(&mut tree, &root) {
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
panic!("Something didn't work... sus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = ::std::env::args().skip(1);
|
||||||
|
let file = args.next().unwrap_or_else(|| String::from("./input"));
|
||||||
|
let solve = args
|
||||||
|
.next()
|
||||||
|
.map(|arg| match arg.as_str() {
|
||||||
|
"first" => SolvePuzzle::First,
|
||||||
|
"second" => SolvePuzzle::Second,
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let file = BufReader::new(File::open(file).expect("Opening file"));
|
||||||
|
match solve {
|
||||||
|
SolvePuzzle::First => println!("{}", solve_first_puzzle(file)),
|
||||||
|
SolvePuzzle::Second => println!("{}", solve_second_puzzle(file)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Operation {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.as_bytes()[0].is_ascii_digit() {
|
||||||
|
Ok(Self::Const(s.parse().expect("A valid const")))
|
||||||
|
} else {
|
||||||
|
// All monkeys are ascii and have length 4
|
||||||
|
let first = s[0..4].to_owned();
|
||||||
|
let op = match &s[5..6] {
|
||||||
|
"+" => Op::Add,
|
||||||
|
"-" => Op::Sub,
|
||||||
|
"*" => Op::Mul,
|
||||||
|
"/" => Op::Div,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let second = s[7..].to_owned();
|
||||||
|
Ok(Self::Simple(op, first, second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_on_first() {
|
||||||
|
let reader = Cursor::new(include_str!("../input.test"));
|
||||||
|
assert_eq!(solve_first_puzzle(reader), 152);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_on_second() {
|
||||||
|
let reader = Cursor::new(include_str!("../input.test"));
|
||||||
|
assert_eq!(solve_second_puzzle(reader), 301);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue