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 day19;
|
||||
mod day20;
|
||||
mod day21;
|
||||
mod template;
|
||||
|
||||
pub use template::DayTrait;
|
||||
|
|
@ -27,7 +28,7 @@ pub mod day_provider {
|
|||
use super::*;
|
||||
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> {
|
||||
match day_num {
|
||||
|
|
@ -51,6 +52,7 @@ pub mod day_provider {
|
|||
18 => Ok(Box::new(day18::Day)),
|
||||
19 => Ok(Box::new(day19::Day)),
|
||||
20 => Ok(Box::new(day20::Day)),
|
||||
21 => Ok(Box::new(day21::Day)),
|
||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue