day21 Finished
This commit is contained in:
parent
4a4117882a
commit
4e64b9786d
4 changed files with 1990 additions and 1 deletions
15
data/day21/example01.txt
Normal file
15
data/day21/example01.txt
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
|
||||||
1579
data/day21/input.txt
Normal file
1579
data/day21/input.txt
Normal file
File diff suppressed because it is too large
Load diff
393
src/days/day21/mod.rs
Normal file
393
src/days/day21/mod.rs
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
use std::{collections::HashMap, ops::Deref, rc::Rc, str::FromStr};
|
||||||
|
|
||||||
|
use super::template::{DayTrait, ResultType};
|
||||||
|
use crate::common::parser::{extract_result, ignore, trim0};
|
||||||
|
use nom::{
|
||||||
|
branch::alt,
|
||||||
|
character::complete::{alpha1, char, i64, multispace0, one_of},
|
||||||
|
error::Error,
|
||||||
|
multi::many0,
|
||||||
|
sequence::{terminated, tuple},
|
||||||
|
Err, IResult, Parser,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
const DAY_NUMBER: usize = 21;
|
||||||
|
|
||||||
|
const ROOT: &'static str = "root";
|
||||||
|
const HUMAN: &'static str = "humn";
|
||||||
|
pub struct Day;
|
||||||
|
|
||||||
|
impl DayTrait for Day {
|
||||||
|
fn get_day_number(&self) -> usize {
|
||||||
|
DAY_NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||||
|
let troop: Troop = lines.parse()?;
|
||||||
|
Ok(ResultType::Integer(troop.value(ROOT)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||||
|
let troop: Troop = lines.parse()?;
|
||||||
|
Ok(ResultType::Integer(troop.make_equal(ROOT)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
struct Name(Rc<str>);
|
||||||
|
|
||||||
|
impl Name {
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Name {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Name {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self(Rc::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum MonkeyError {
|
||||||
|
#[error("Not a valid description: {0}")]
|
||||||
|
ParsingError(String),
|
||||||
|
|
||||||
|
#[error("Unknown Monkey: {0}")]
|
||||||
|
UnknownMonkey(String),
|
||||||
|
|
||||||
|
#[error("Not an algebraic Operation")]
|
||||||
|
NoAlgebraicOperation,
|
||||||
|
|
||||||
|
#[error("There are too many Humans in this input")]
|
||||||
|
TooManyHumans,
|
||||||
|
|
||||||
|
#[error("Unknown operand: {0}")]
|
||||||
|
UnknownOperand(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Err<Error<&str>>> for MonkeyError {
|
||||||
|
fn from(error: Err<Error<&str>>) -> Self {
|
||||||
|
MonkeyError::ParsingError(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum MonkeyJob {
|
||||||
|
Yell(i64),
|
||||||
|
Add(Name, Name),
|
||||||
|
Sub(Name, Name),
|
||||||
|
Mul(Name, Name),
|
||||||
|
Div(Name, Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MonkeyJob {
|
||||||
|
pub fn new<N1: Into<Name>, N2: Into<Name>>(
|
||||||
|
operand: char,
|
||||||
|
name1: N1,
|
||||||
|
name2: N2,
|
||||||
|
) -> Result<Self, MonkeyError> {
|
||||||
|
match operand {
|
||||||
|
'+' => Ok(MonkeyJob::Add(name1.into(), name2.into())),
|
||||||
|
'-' => Ok(MonkeyJob::Sub(name1.into(), name2.into())),
|
||||||
|
'*' => Ok(MonkeyJob::Mul(name1.into(), name2.into())),
|
||||||
|
'/' => Ok(MonkeyJob::Div(name1.into(), name2.into())),
|
||||||
|
c => Err(MonkeyError::UnknownOperand(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_operation(input: &str) -> IResult<&str, MonkeyJob> {
|
||||||
|
tuple((alpha1, trim0(one_of("+-*/")), alpha1))
|
||||||
|
.map(|(name1, operand, name2)| MonkeyJob::new(operand, name1, name2).unwrap())
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(input: &str) -> IResult<&str, MonkeyJob> {
|
||||||
|
trim0(alt((
|
||||||
|
i64.map(|val| MonkeyJob::Yell(val)),
|
||||||
|
MonkeyJob::parse_operation,
|
||||||
|
)))
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&self, troop: &Troop) -> Result<i64, MonkeyError> {
|
||||||
|
Ok(match self {
|
||||||
|
MonkeyJob::Yell(value) => *value,
|
||||||
|
MonkeyJob::Add(name1, name2) => {
|
||||||
|
troop.value(name1.clone())? + troop.value(name2.clone())?
|
||||||
|
}
|
||||||
|
MonkeyJob::Sub(name1, name2) => {
|
||||||
|
troop.value(name1.clone())? - troop.value(name2.clone())?
|
||||||
|
}
|
||||||
|
MonkeyJob::Mul(name1, name2) => {
|
||||||
|
troop.value(name1.clone())? * troop.value(name2.clone())?
|
||||||
|
}
|
||||||
|
MonkeyJob::Div(name1, name2) => {
|
||||||
|
troop.value(name1.clone())? / troop.value(name2.clone())?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_names(&self) -> Result<(Name, Name), MonkeyError> {
|
||||||
|
match self {
|
||||||
|
MonkeyJob::Add(name1, name2)
|
||||||
|
| MonkeyJob::Sub(name1, name2)
|
||||||
|
| MonkeyJob::Mul(name1, name2)
|
||||||
|
| MonkeyJob::Div(name1, name2) => Ok((name1.clone(), name2.clone())),
|
||||||
|
|
||||||
|
MonkeyJob::Yell(_) => Err(MonkeyError::NoAlgebraicOperation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_reverse(&self, troop: &Troop) -> Result<HumanJob, MonkeyError> {
|
||||||
|
match self {
|
||||||
|
MonkeyJob::Yell(value) => Ok(HumanJob::Const(*value)),
|
||||||
|
|
||||||
|
MonkeyJob::Add(name1, name2) => match troop
|
||||||
|
.shouting_match(name1.clone(), name2.clone())?
|
||||||
|
{
|
||||||
|
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 + val2)),
|
||||||
|
(other, HumanJob::Const(val2)) => Ok(HumanJob::Add(Box::new(other), val2)),
|
||||||
|
(HumanJob::Const(val1), other) => Ok(HumanJob::Add(Box::new(other), val1)),
|
||||||
|
_ => Err(MonkeyError::TooManyHumans),
|
||||||
|
},
|
||||||
|
MonkeyJob::Sub(name1, name2) => match troop
|
||||||
|
.shouting_match(name1.clone(), name2.clone())?
|
||||||
|
{
|
||||||
|
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 - val2)),
|
||||||
|
(other, HumanJob::Const(val2)) => Ok(HumanJob::Sub1(Box::new(other), val2)),
|
||||||
|
(HumanJob::Const(val1), other) => Ok(HumanJob::Sub2(val1, Box::new(other))),
|
||||||
|
_ => Err(MonkeyError::TooManyHumans),
|
||||||
|
},
|
||||||
|
MonkeyJob::Mul(name1, name2) => match troop
|
||||||
|
.shouting_match(name1.clone(), name2.clone())?
|
||||||
|
{
|
||||||
|
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 * val2)),
|
||||||
|
(other, HumanJob::Const(val2)) => Ok(HumanJob::Mul(Box::new(other), val2)),
|
||||||
|
(HumanJob::Const(val1), other) => Ok(HumanJob::Mul(Box::new(other), val1)),
|
||||||
|
_ => Err(MonkeyError::TooManyHumans),
|
||||||
|
},
|
||||||
|
MonkeyJob::Div(name1, name2) => match troop
|
||||||
|
.shouting_match(name1.clone(), name2.clone())?
|
||||||
|
{
|
||||||
|
(HumanJob::Const(val1), HumanJob::Const(val2)) => Ok(HumanJob::Const(val1 / val2)),
|
||||||
|
(other, HumanJob::Const(val2)) => Ok(HumanJob::Div1(Box::new(other), val2)),
|
||||||
|
(HumanJob::Const(val1), other) => Ok(HumanJob::Div2(val1, Box::new(other))),
|
||||||
|
_ => Err(MonkeyError::TooManyHumans),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HumanJob {
|
||||||
|
Human,
|
||||||
|
Const(i64),
|
||||||
|
Add(Box<HumanJob>, i64),
|
||||||
|
Sub1(Box<HumanJob>, i64),
|
||||||
|
Sub2(i64, Box<HumanJob>),
|
||||||
|
Mul(Box<HumanJob>, i64),
|
||||||
|
Div1(Box<HumanJob>, i64),
|
||||||
|
Div2(i64, Box<HumanJob>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HumanJob {
|
||||||
|
fn calc(&self, result: i64) -> i64 {
|
||||||
|
match self {
|
||||||
|
HumanJob::Human => result,
|
||||||
|
HumanJob::Add(job, value) => job.calc(result - value),
|
||||||
|
HumanJob::Sub1(job, value) => job.calc(result + value),
|
||||||
|
HumanJob::Sub2(value, job) => job.calc(value - result),
|
||||||
|
HumanJob::Mul(job, value) => job.calc(result / value),
|
||||||
|
HumanJob::Div1(job, value) => job.calc(result * value),
|
||||||
|
HumanJob::Div2(value, job) => job.calc(value / result),
|
||||||
|
|
||||||
|
HumanJob::Const(_) => {
|
||||||
|
unreachable!("There can be not Const in a legal HumanJob tree")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
struct Monkey {
|
||||||
|
name: Name,
|
||||||
|
job: MonkeyJob,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Monkey {
|
||||||
|
fn new<N: Into<Name>>(name: N, job: MonkeyJob) -> Self {
|
||||||
|
Monkey {
|
||||||
|
name: name.into(),
|
||||||
|
job,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(input: &str) -> IResult<&str, Monkey> {
|
||||||
|
let (input, name) = trim0(alpha1)(input)?;
|
||||||
|
let input = ignore(trim0(char(':')))(input)?;
|
||||||
|
let (input, job) = MonkeyJob::parse(input)?;
|
||||||
|
Ok((input, Monkey::new(name, job)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shouting(&self, troop: &Troop) -> Result<i64, MonkeyError> {
|
||||||
|
self.job.perform(troop)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse_shouting(&self, troop: &Troop) -> Result<HumanJob, MonkeyError> {
|
||||||
|
if self.name.as_str() == HUMAN {
|
||||||
|
return Ok(HumanJob::Human);
|
||||||
|
}
|
||||||
|
self.job.perform_reverse(troop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Monkey {
|
||||||
|
type Err = MonkeyError;
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(extract_result(Monkey::parse)(input)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Troop {
|
||||||
|
monkeys: HashMap<Name, Monkey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Troop {
|
||||||
|
pub fn get_monkey<N: Into<Name>>(&self, name: N) -> Result<&Monkey, MonkeyError> {
|
||||||
|
let name = name.into();
|
||||||
|
self.monkeys
|
||||||
|
.get(&name)
|
||||||
|
.ok_or_else(|| MonkeyError::UnknownMonkey(String::from(name.as_str())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value<N: Into<Name>>(&self, name: N) -> Result<i64, MonkeyError> {
|
||||||
|
self.get_monkey(name)?.shouting(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_equal<N: Into<Name>>(&self, name: N) -> Result<i64, MonkeyError> {
|
||||||
|
let monkey = self.get_monkey(name)?;
|
||||||
|
let (name1, name2) = monkey.job.get_names()?;
|
||||||
|
let monkey1 = self.get_monkey(name1)?;
|
||||||
|
let monkey2 = self.get_monkey(name2)?;
|
||||||
|
match (
|
||||||
|
monkey1.reverse_shouting(self)?,
|
||||||
|
monkey2.reverse_shouting(self)?,
|
||||||
|
) {
|
||||||
|
(job, HumanJob::Const(value)) | (HumanJob::Const(value), job) => Ok(job.calc(value)),
|
||||||
|
_ => Err(MonkeyError::TooManyHumans),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shouting_match<N1: Into<Name>, N2: Into<Name>>(
|
||||||
|
&self,
|
||||||
|
name1: N1,
|
||||||
|
name2: N2,
|
||||||
|
) -> Result<(HumanJob, HumanJob), MonkeyError> {
|
||||||
|
let monkey1 = self.get_monkey(name1)?;
|
||||||
|
let monkey2 = self.get_monkey(name2)?;
|
||||||
|
Ok((
|
||||||
|
monkey1.reverse_shouting(self)?,
|
||||||
|
monkey2.reverse_shouting(self)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Troop {
|
||||||
|
type Err = MonkeyError;
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
let monkeys = extract_result(many0(terminated(Monkey::parse, multispace0)))(input)?;
|
||||||
|
let monkeys: HashMap<Name, Monkey> = monkeys
|
||||||
|
.into_iter()
|
||||||
|
.map(|monkey| (monkey.name.clone(), monkey))
|
||||||
|
.collect();
|
||||||
|
Ok(Troop { monkeys })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::common::file::read_string;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part1() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let expected = ResultType::Integer(152);
|
||||||
|
let result = day.part1(&lines)?;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part2() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let expected = ResultType::Integer(301);
|
||||||
|
let result = day.part2(&lines)?;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_lines() -> Result<()> {
|
||||||
|
let input = "root: pppw + sjmn";
|
||||||
|
let expected = Monkey {
|
||||||
|
name: Name::from("root"),
|
||||||
|
job: MonkeyJob::Add(Name::from("pppw"), Name::from("sjmn")),
|
||||||
|
};
|
||||||
|
let result: Monkey = input.parse()?;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_troop() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let troop: Troop = lines.parse()?;
|
||||||
|
assert_eq!(troop.monkeys.len(), 15);
|
||||||
|
assert_eq!(
|
||||||
|
troop.monkeys[&Name::from("sjmn")],
|
||||||
|
Monkey::new(
|
||||||
|
"sjmn",
|
||||||
|
MonkeyJob::Mul(Name::from("drzm"), Name::from("dbpl"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn values() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let troop: Troop = lines.parse()?;
|
||||||
|
assert_eq!(troop.value(Name::from("lfqf"))?, 4);
|
||||||
|
assert_eq!(troop.value(Name::from("root"))?, 152);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_equal() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let troop: Troop = lines.parse()?;
|
||||||
|
assert_eq!(troop.make_equal(Name::from("root"))?, 301);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ mod day17;
|
||||||
mod day18;
|
mod day18;
|
||||||
mod day19;
|
mod day19;
|
||||||
mod day20;
|
mod day20;
|
||||||
|
mod day21;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
pub use template::DayTrait;
|
pub use template::DayTrait;
|
||||||
|
|
@ -27,7 +28,7 @@ pub mod day_provider {
|
||||||
use super::*;
|
use super::*;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
const MAX_DAY: usize = 20;
|
const MAX_DAY: usize = 21;
|
||||||
|
|
||||||
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
pub fn get_day(day_num: usize) -> Result<Box<dyn DayTrait>, ProviderError> {
|
||||||
match day_num {
|
match day_num {
|
||||||
|
|
@ -51,6 +52,7 @@ pub mod day_provider {
|
||||||
18 => Ok(Box::new(day18::Day)),
|
18 => Ok(Box::new(day18::Day)),
|
||||||
19 => Ok(Box::new(day19::Day)),
|
19 => Ok(Box::new(day19::Day)),
|
||||||
20 => Ok(Box::new(day20::Day)),
|
20 => Ok(Box::new(day20::Day)),
|
||||||
|
21 => Ok(Box::new(day21::Day)),
|
||||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue