day21 Finished

This commit is contained in:
Rüdiger Ludwig 2023-08-05 19:40:25 +02:00
parent 4a4117882a
commit 4e64b9786d
4 changed files with 1990 additions and 1 deletions

15
data/day21/example01.txt Normal file
View 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

File diff suppressed because it is too large Load diff

393
src/days/day21/mod.rs Normal file
View 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(())
}
}

View file

@ -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)),
} }
} }