day 19 finished
This commit is contained in:
parent
b00835c25e
commit
062ede1df1
4 changed files with 631 additions and 1 deletions
11
data/day19/example01.txt
Normal file
11
data/day19/example01.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Blueprint 1:
|
||||||
|
Each ore robot costs 4 ore.
|
||||||
|
Each clay robot costs 2 ore.
|
||||||
|
Each obsidian robot costs 3 ore and 14 clay.
|
||||||
|
Each geode robot costs 2 ore and 7 obsidian.
|
||||||
|
|
||||||
|
Blueprint 2:
|
||||||
|
Each ore robot costs 2 ore.
|
||||||
|
Each clay robot costs 3 ore.
|
||||||
|
Each obsidian robot costs 3 ore and 8 clay.
|
||||||
|
Each geode robot costs 3 ore and 12 obsidian.
|
||||||
30
data/day19/input.txt
Normal file
30
data/day19/input.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 4 ore and 11 obsidian.
|
||||||
|
Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||||
|
Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 10 obsidian.
|
||||||
|
Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian.
|
||||||
|
Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 4 ore and 20 obsidian.
|
||||||
|
Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 2 ore and 18 obsidian.
|
||||||
|
Blueprint 7: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 20 obsidian.
|
||||||
|
Blueprint 8: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 4 ore and 17 obsidian.
|
||||||
|
Blueprint 9: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 4 ore and 7 obsidian.
|
||||||
|
Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian.
|
||||||
|
Blueprint 11: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian.
|
||||||
|
Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 18 obsidian.
|
||||||
|
Blueprint 13: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 2 ore and 20 obsidian.
|
||||||
|
Blueprint 14: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 14 obsidian.
|
||||||
|
Blueprint 15: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 16 obsidian.
|
||||||
|
Blueprint 16: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 3 ore and 19 obsidian.
|
||||||
|
Blueprint 17: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 19 obsidian.
|
||||||
|
Blueprint 18: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian.
|
||||||
|
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 3 ore and 10 obsidian.
|
||||||
|
Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 12 obsidian.
|
||||||
|
Blueprint 21: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 17 obsidian.
|
||||||
|
Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||||
|
Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 3 ore and 9 obsidian.
|
||||||
|
Blueprint 24: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 2 ore and 10 obsidian.
|
||||||
|
Blueprint 25: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 8 obsidian.
|
||||||
|
Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 17 obsidian.
|
||||||
|
Blueprint 27: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 3 ore and 8 obsidian.
|
||||||
|
Blueprint 28: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||||
|
Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 13 obsidian.
|
||||||
|
Blueprint 30: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 4 ore and 11 obsidian.
|
||||||
587
src/days/day19/mod.rs
Normal file
587
src/days/day19/mod.rs
Normal file
|
|
@ -0,0 +1,587 @@
|
||||||
|
use super::template::{DayTrait, ResultType};
|
||||||
|
use crate::common::parser::{extract_result, ignore, trim0, trim1, trim_left1};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nom::{
|
||||||
|
branch::alt,
|
||||||
|
bytes::complete::tag,
|
||||||
|
character::complete::{char, i64, multispace0, u32},
|
||||||
|
combinator::{value, verify},
|
||||||
|
error::Error,
|
||||||
|
multi::{count, many0, separated_list1},
|
||||||
|
sequence::{preceded, tuple},
|
||||||
|
Err, IResult, Parser,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::{BinaryHeap, HashMap},
|
||||||
|
mem,
|
||||||
|
ops::{Add, IndexMut, Sub},
|
||||||
|
sync::mpsc,
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
use std::{ops::Index, str::FromStr};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
const DAY_NUMBER: usize = 19;
|
||||||
|
|
||||||
|
const NUMBER_MATERIALS: usize = 4;
|
||||||
|
const USE_THREADED: bool = true;
|
||||||
|
|
||||||
|
pub struct Day;
|
||||||
|
|
||||||
|
impl DayTrait for Day {
|
||||||
|
fn get_day_number(&self) -> usize {
|
||||||
|
DAY_NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||||
|
let cabinet: Cabinet = lines.parse()?;
|
||||||
|
let result = if USE_THREADED {
|
||||||
|
cabinet.threaded_quality_level(24)?
|
||||||
|
} else {
|
||||||
|
cabinet.quality_level(24)?
|
||||||
|
};
|
||||||
|
Ok(ResultType::Integer(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||||
|
let cabinet: Cabinet = lines.parse()?;
|
||||||
|
let result = if USE_THREADED {
|
||||||
|
cabinet.threaded_reduced_quality(3, 32)?
|
||||||
|
} else {
|
||||||
|
cabinet.reduced_quality(3, 32)?
|
||||||
|
};
|
||||||
|
Ok(ResultType::Integer(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum RobotError {
|
||||||
|
#[error("Not a valid description: {0}")]
|
||||||
|
ParsingError(String),
|
||||||
|
|
||||||
|
#[error("No optimum was found")]
|
||||||
|
NoOptimumFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Err<Error<&str>>> for RobotError {
|
||||||
|
fn from(error: Err<Error<&str>>) -> Self {
|
||||||
|
RobotError::ParsingError(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
|
enum Material {
|
||||||
|
Geode = 0,
|
||||||
|
Obsidian = 1,
|
||||||
|
Clay = 2,
|
||||||
|
Ore = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material {
|
||||||
|
pub fn prev(&self) -> Option<Material> {
|
||||||
|
match self {
|
||||||
|
Material::Geode => None,
|
||||||
|
Material::Obsidian => Some(Material::Geode),
|
||||||
|
Material::Clay => Some(Material::Obsidian),
|
||||||
|
Material::Ore => Some(Material::Clay),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Option<Material> {
|
||||||
|
match self {
|
||||||
|
Material::Geode => Some(Material::Obsidian),
|
||||||
|
Material::Obsidian => Some(Material::Clay),
|
||||||
|
Material::Clay => Some(Material::Ore),
|
||||||
|
Material::Ore => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: &str) -> IResult<&str, Material> {
|
||||||
|
alt((
|
||||||
|
value(Material::Ore, tag("ore")),
|
||||||
|
value(Material::Clay, tag("clay")),
|
||||||
|
value(Material::Obsidian, tag("obsidian")),
|
||||||
|
value(Material::Geode, tag("geode")),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct Ingredients([i64; NUMBER_MATERIALS]);
|
||||||
|
|
||||||
|
impl Ingredients {
|
||||||
|
pub fn new(items: Vec<(i64, Material)>) -> Self {
|
||||||
|
let mut ingredients = [0; NUMBER_MATERIALS];
|
||||||
|
for (amount, material) in items {
|
||||||
|
ingredients[material as usize] = amount;
|
||||||
|
}
|
||||||
|
Ingredients(ingredients)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_non_negative(&self) -> bool {
|
||||||
|
self.0.iter().all(|item| *item >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc(&self, mat: Material) -> Ingredients {
|
||||||
|
let mut next = self.0.clone();
|
||||||
|
next[mat as usize] += 1;
|
||||||
|
Ingredients(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pos_max(&self, other: &Ingredients) -> Self {
|
||||||
|
let next = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.zip(other.0.iter())
|
||||||
|
.map(|(a, b)| *a.max(b))
|
||||||
|
.collect_vec()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
Ingredients(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none_smaller(&self, other: &Ingredients) -> bool {
|
||||||
|
self.0.iter().zip(other.0.iter()).all(|(a, b)| a >= b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<Material> for Ingredients {
|
||||||
|
type Output = i64;
|
||||||
|
|
||||||
|
fn index(&self, index: Material) -> &Self::Output {
|
||||||
|
&self.0[index as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<Material> for Ingredients {
|
||||||
|
fn index_mut(&mut self, index: Material) -> &mut Self::Output {
|
||||||
|
&mut self.0[index as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Ingredients> for Ingredients {
|
||||||
|
type Output = Ingredients;
|
||||||
|
|
||||||
|
fn add(self, rhs: &Ingredients) -> Self::Output {
|
||||||
|
let mut result = self.clone();
|
||||||
|
for pos in 0..NUMBER_MATERIALS {
|
||||||
|
result.0[pos] += rhs.0[pos];
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Ingredients> for &Ingredients {
|
||||||
|
type Output = Ingredients;
|
||||||
|
|
||||||
|
fn add(self, rhs: &Ingredients) -> Self::Output {
|
||||||
|
let mut result = self.clone();
|
||||||
|
for pos in 0..NUMBER_MATERIALS {
|
||||||
|
result.0[pos] += rhs.0[pos];
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<&Ingredients> for Ingredients {
|
||||||
|
type Output = Ingredients;
|
||||||
|
|
||||||
|
fn sub(self, rhs: &Self) -> Self::Output {
|
||||||
|
let mut result = self.clone();
|
||||||
|
for pos in 0..NUMBER_MATERIALS {
|
||||||
|
result.0[pos] -= rhs.0[pos];
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<&Ingredients> for &Ingredients {
|
||||||
|
type Output = Ingredients;
|
||||||
|
|
||||||
|
fn sub(self, rhs: &Ingredients) -> Self::Output {
|
||||||
|
let mut result = self.clone();
|
||||||
|
for pos in 0..NUMBER_MATERIALS {
|
||||||
|
result.0[pos] -= rhs.0[pos];
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct Blueprint {
|
||||||
|
id: usize,
|
||||||
|
material: [Ingredients; NUMBER_MATERIALS],
|
||||||
|
max_robots: Ingredients,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Blueprint {
|
||||||
|
pub fn new(id: usize, material: [Ingredients; NUMBER_MATERIALS]) -> Self {
|
||||||
|
let mut max_robots = material[Material::Geode as usize]
|
||||||
|
.pos_max(&material[Material::Obsidian as usize])
|
||||||
|
.pos_max(&material[Material::Clay as usize]);
|
||||||
|
max_robots[Material::Geode] = i32::MAX as i64;
|
||||||
|
|
||||||
|
Blueprint {
|
||||||
|
id,
|
||||||
|
material,
|
||||||
|
max_robots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ingredients_for(&self, mat: Material) -> &Ingredients {
|
||||||
|
&self.material[mat as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_line(input: &str) -> IResult<&str, (Material, Ingredients)> {
|
||||||
|
let input = ignore(tag("Each"))(input)?;
|
||||||
|
let (input, robot) = trim1(Material::parse)(input)?;
|
||||||
|
let input = ignore(tag("robot costs"))(input)?;
|
||||||
|
let (input, ingredients) =
|
||||||
|
separated_list1(tag("and"), tuple((trim0(i64), trim0(Material::parse))))(input)?;
|
||||||
|
let input = ignore(char('.'))(input)?;
|
||||||
|
Ok((input, (robot, Ingredients::new(ingredients))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(start: &str) -> IResult<&str, Self> {
|
||||||
|
let input = ignore(tag("Blueprint"))(start)?;
|
||||||
|
let (input, id) = trim_left1(u32.map(|v| v as usize))(input)?;
|
||||||
|
let input = ignore(char(':'))(input)?;
|
||||||
|
let (input, robots) = verify(
|
||||||
|
count(
|
||||||
|
preceded(multispace0, Blueprint::parse_line),
|
||||||
|
NUMBER_MATERIALS,
|
||||||
|
),
|
||||||
|
|robots: &[(Material, Ingredients)]| {
|
||||||
|
robots.iter().map(|(material, _)| material).all_unique()
|
||||||
|
},
|
||||||
|
)(input)?;
|
||||||
|
|
||||||
|
let material: [Ingredients; NUMBER_MATERIALS] = robots
|
||||||
|
.into_iter()
|
||||||
|
.sorted_by_key(|(material, _)| *material)
|
||||||
|
.map(|(_, ingreditents)| ingreditents)
|
||||||
|
.collect_vec()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok((input, Blueprint::new(id, material)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulate(&self, max_time: usize) -> Result<(usize, i64), RobotError> {
|
||||||
|
let simulation = Simulation::new(max_time);
|
||||||
|
let mut queue = BinaryHeap::new();
|
||||||
|
let mut seen: HashMap<(Ingredients, usize), Ingredients> = HashMap::new();
|
||||||
|
queue.push(simulation);
|
||||||
|
|
||||||
|
while let Some(current) = queue.pop() {
|
||||||
|
if current.time == 0 {
|
||||||
|
return Ok((self.id, current.material(Material::Geode)));
|
||||||
|
}
|
||||||
|
let key = (current.all_robots().clone(), current.time);
|
||||||
|
if let Some(last) = seen.get(&key) {
|
||||||
|
if last.none_smaller(current.all_material()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seen.insert(key, current.all_material().clone());
|
||||||
|
|
||||||
|
queue.extend(current.next_round(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(RobotError::NoOptimumFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Blueprint {
|
||||||
|
type Err = RobotError;
|
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(extract_result(Blueprint::parse)(input)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cabinet(Vec<Blueprint>);
|
||||||
|
|
||||||
|
impl FromStr for Cabinet {
|
||||||
|
type Err = RobotError;
|
||||||
|
|
||||||
|
fn from_str(line: &str) -> Result<Self, Self::Err> {
|
||||||
|
let blueprints = extract_result(many0(preceded(multispace0, Blueprint::parse)))(line)?;
|
||||||
|
Ok(Cabinet(blueprints))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cabinet {
|
||||||
|
pub fn quality_level(&self, max_time: usize) -> Result<i64, RobotError> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|blueprint| blueprint.simulate(max_time))
|
||||||
|
.map_ok(|(id, geodes)| id as i64 * geodes)
|
||||||
|
.fold_ok(0, |a, b| a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reduced_quality(&self, count: usize, max_time: usize) -> Result<i64, RobotError> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.take(count)
|
||||||
|
.map(|blueprint| blueprint.simulate(max_time))
|
||||||
|
.map_ok(|(_, geodes)| geodes)
|
||||||
|
.fold_ok(1, |a, b| a * b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn threaded_quality_level(self, max_time: usize) -> Result<i64, RobotError> {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
for blueprint in self.0 {
|
||||||
|
let sender = sender.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let result = blueprint.simulate(max_time);
|
||||||
|
let _ = sender.send(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
mem::drop(sender);
|
||||||
|
|
||||||
|
receiver
|
||||||
|
.into_iter()
|
||||||
|
.map_ok(|(id, geodes)| id as i64 * geodes)
|
||||||
|
.fold_ok(0, |a, b| a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn threaded_reduced_quality(
|
||||||
|
self,
|
||||||
|
count: usize,
|
||||||
|
max_time: usize,
|
||||||
|
) -> Result<i64, RobotError> {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let mut done = 0;
|
||||||
|
for blueprint in self.0 {
|
||||||
|
let sender = sender.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let result = blueprint.simulate(max_time);
|
||||||
|
let _ = sender.send(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
done = done + 1;
|
||||||
|
if done >= count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mem::drop(sender);
|
||||||
|
|
||||||
|
receiver
|
||||||
|
.into_iter()
|
||||||
|
.map_ok(|(_, geodes)| geodes)
|
||||||
|
.fold_ok(1, |a, b| a * b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Simulation {
|
||||||
|
time: usize,
|
||||||
|
robots: Ingredients,
|
||||||
|
material: Ingredients,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Simulation {
|
||||||
|
pub fn new(time: usize) -> Self {
|
||||||
|
let _robots = Ingredients::new(vec![(1, Material::Ore)]);
|
||||||
|
let _material = Ingredients::new(vec![]);
|
||||||
|
Self {
|
||||||
|
time,
|
||||||
|
robots: _robots,
|
||||||
|
material: _material,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn all_robots(&self) -> &Ingredients {
|
||||||
|
&self.robots
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn robots_for(&self, mat: Material) -> i64 {
|
||||||
|
self.robots[mat]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn all_material(&self) -> &Ingredients {
|
||||||
|
&self.material
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn material(&self, mat: Material) -> i64 {
|
||||||
|
self.material[mat]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_production(mut self) -> Self {
|
||||||
|
self.time = self.time - 1;
|
||||||
|
self.material = self.material + &self.robots;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_create(&self, blueprint: &Blueprint, mat: Material) -> Option<Simulation> {
|
||||||
|
let rest = self.all_material() - blueprint.ingredients_for(mat);
|
||||||
|
if rest.is_non_negative() {
|
||||||
|
let robots = self.all_robots().inc(mat);
|
||||||
|
let material = rest + self.all_robots();
|
||||||
|
Some(Simulation {
|
||||||
|
time: self.time - 1,
|
||||||
|
robots,
|
||||||
|
material,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_round(self, blueprint: &Blueprint) -> Vec<Simulation> {
|
||||||
|
let mut result = vec![];
|
||||||
|
if let Some(next) = self.check_create(blueprint, Material::Geode) {
|
||||||
|
result.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current = Some(Material::Obsidian);
|
||||||
|
while let Some(mat) = current {
|
||||||
|
if self.robots_for(mat) < blueprint.max_robots[mat]
|
||||||
|
&& self.material(mat) <= 4 * blueprint.max_robots[mat] / 3
|
||||||
|
{
|
||||||
|
if let Some(next) = self.check_create(blueprint, mat) {
|
||||||
|
result.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = mat.next();
|
||||||
|
}
|
||||||
|
if result.len() <= 1 {
|
||||||
|
result.push(self.no_production());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Simulation {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.time == other.time && self.material == other.material
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Simulation {}
|
||||||
|
|
||||||
|
impl PartialOrd for Simulation {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Simulation {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
match self.time.cmp(&other.time) {
|
||||||
|
core::cmp::Ordering::Equal => {}
|
||||||
|
ord => return ord,
|
||||||
|
}
|
||||||
|
self.material.cmp(&other.material)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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(33);
|
||||||
|
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(56 * 62);
|
||||||
|
let result = day.part2(&lines)?;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() -> Result<()> {
|
||||||
|
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||||
|
let expected = Blueprint::new(
|
||||||
|
1,
|
||||||
|
[
|
||||||
|
Ingredients([0, 7, 0, 2]),
|
||||||
|
Ingredients([0, 0, 14, 3]),
|
||||||
|
Ingredients([0, 0, 0, 2]),
|
||||||
|
Ingredients([0, 0, 0, 4]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let result: Blueprint = line.parse()?;
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_faulty() {
|
||||||
|
let line = "Blueprint 1: Each ore robot costs 4 ore. Each ore robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||||
|
let result: Result<Blueprint, _> = line.parse();
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_many() -> Result<()> {
|
||||||
|
let day = Day {};
|
||||||
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||||
|
let cabinet: Cabinet = lines.parse()?;
|
||||||
|
assert_eq!(cabinet.0.len(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_one() -> Result<()> {
|
||||||
|
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||||
|
let blueprint: Blueprint = line.parse()?;
|
||||||
|
let result = blueprint.simulate(24)?;
|
||||||
|
assert_eq!(result, (1, 9));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_two() -> Result<()> {
|
||||||
|
let line = "Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.";
|
||||||
|
let blueprint: Blueprint = line.parse()?;
|
||||||
|
let result = blueprint.simulate(24)?;
|
||||||
|
assert_eq!(result, (2, 12));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_one_two() -> Result<()> {
|
||||||
|
let line = "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.";
|
||||||
|
let blueprint: Blueprint = line.parse()?;
|
||||||
|
let result = blueprint.simulate(32)?;
|
||||||
|
assert_eq!(result, (1, 56));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_two_two() -> Result<()> {
|
||||||
|
let line = "Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.";
|
||||||
|
|
||||||
|
let blueprint: Blueprint = line.parse()?;
|
||||||
|
let result = blueprint.simulate(32)?;
|
||||||
|
assert_eq!(result, (2, 62));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ mod day15;
|
||||||
mod day16;
|
mod day16;
|
||||||
mod day17;
|
mod day17;
|
||||||
mod day18;
|
mod day18;
|
||||||
|
mod day19;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
pub use template::DayTrait;
|
pub use template::DayTrait;
|
||||||
|
|
@ -25,7 +26,7 @@ pub mod day_provider {
|
||||||
use super::*;
|
use super::*;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
const MAX_DAY: usize = 18;
|
const MAX_DAY: usize = 19;
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -47,6 +48,7 @@ pub mod day_provider {
|
||||||
16 => Ok(Box::new(day16::Day)),
|
16 => Ok(Box::new(day16::Day)),
|
||||||
17 => Ok(Box::new(day17::Day)),
|
17 => Ok(Box::new(day17::Day)),
|
||||||
18 => Ok(Box::new(day18::Day)),
|
18 => Ok(Box::new(day18::Day)),
|
||||||
|
19 => Ok(Box::new(day19::Day)),
|
||||||
_ => Err(ProviderError::InvalidNumber(day_num)),
|
_ => Err(ProviderError::InvalidNumber(day_num)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue