Cleanup for 2022 days 11 through 21: Turned into a lib and introduced parse errors
This commit is contained in:
parent
c7852c9791
commit
fcb2fed515
26 changed files with 1095 additions and 879 deletions
232
2022/day19-not_enough_minerals/src/lib.rs
Normal file
232
2022/day19-not_enough_minerals/src/lib.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use core::fmt::Display;
|
||||
use std::num::ParseIntError;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError<'a> {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
LineMalformed(&'a str),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError<'_> {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Blueprint {
|
||||
id: usize,
|
||||
ore_robot_cost: u8,
|
||||
clay_robot_cost: u8,
|
||||
obsidian_robot_ore_cost: u8,
|
||||
obsidian_robot_clay_cost: u8,
|
||||
geode_robot_ore_cost: u8,
|
||||
geode_robot_obsidian_cost: u8,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Blueprint {
|
||||
type Error = ParseError<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let components: Vec<&str> = value.split(' ').collect();
|
||||
if components.len() != 32 {
|
||||
return Err(Self::Error::LineMalformed(value));
|
||||
}
|
||||
Ok(Self {
|
||||
id: components[1][..components[1].len()-1].parse()?,
|
||||
ore_robot_cost: components[6].parse()?,
|
||||
clay_robot_cost: components[12].parse()?,
|
||||
obsidian_robot_ore_cost: components[18].parse()?,
|
||||
obsidian_robot_clay_cost: components[21].parse()?,
|
||||
geode_robot_ore_cost: components[27].parse()?,
|
||||
geode_robot_obsidian_cost: components[30].parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn more_robots_required(robot_count: u8, stock: u8, max_demand: u8, time_remaining: u8) -> bool {
|
||||
(robot_count as usize * time_remaining as usize + stock as usize) < (max_demand as usize * time_remaining as usize)
|
||||
}
|
||||
|
||||
impl Blueprint {
|
||||
fn collect_geodes(&self, time: u8) -> u8 {
|
||||
self.try_bfs(time)
|
||||
}
|
||||
|
||||
fn try_bfs(&self, time: u8) -> u8 {
|
||||
let mut open_set = VecDeque::from([(Inventory::new(), time)]);
|
||||
let mut mem = HashMap::new();
|
||||
let mut best = 0;
|
||||
|
||||
while let Some((inventory, time_remaining)) = open_set.pop_front() {
|
||||
if time_remaining == 0 {
|
||||
best = best.max(inventory.geodes);
|
||||
} else if let Some(best_time) = mem.get(&inventory.as_arr()) {
|
||||
if *best_time >= time_remaining {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
mem.insert(inventory.as_arr(), time_remaining);
|
||||
// let inventory = inventory.collect();
|
||||
|
||||
// Always buy a Geode Robot if we can afford it and there is at least 1 unit of time remaining
|
||||
// (so it will produce at least once)
|
||||
if time_remaining > 1 && inventory.ore >= self.geode_robot_ore_cost && inventory.obsidian >= self.geode_robot_obsidian_cost {
|
||||
let mut new_inventory = inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.geode_robot_ore_cost;
|
||||
new_inventory.obsidian -= self.geode_robot_obsidian_cost;
|
||||
new_inventory.geode_robots += 1;
|
||||
open_set.push_back((new_inventory, time_remaining-1));
|
||||
} else {
|
||||
// Save Ressources only if there is any robot we can't afford, but we already produce the
|
||||
// required ressource, or we are close to the end
|
||||
if time_remaining < 4 || inventory.ore < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() ||
|
||||
inventory.clay < self.obsidian_robot_clay_cost && inventory.clay_robots > 0 ||
|
||||
inventory.obsidian < self.geode_robot_obsidian_cost && inventory.obsidian_robots > 0
|
||||
{
|
||||
let mut new_inventory = inventory;
|
||||
new_inventory.collect();
|
||||
open_set.push_back((new_inventory, time_remaining-1));
|
||||
}
|
||||
// Buy an Ore Robot if
|
||||
// - we can afford it, and
|
||||
// - we don't already produce enough Ore for any other Robot each round, and
|
||||
// - there are at least 2 rounds left (1 to produce and buy a Geode Robot, 1 for that
|
||||
// to produce).
|
||||
// if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && inventory.ore_robots < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() {
|
||||
if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && more_robots_required(inventory.ore_robots, inventory.ore, *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap(), time_remaining-1) {
|
||||
let mut new_inventory = inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.ore_robot_cost;
|
||||
new_inventory.ore_robots += 1;
|
||||
open_set.push_back((new_inventory, time_remaining-1));
|
||||
}
|
||||
// Buy a Clay Robot if
|
||||
// - we can afford it, and
|
||||
// - we don't already produce enough Clay for an Obsidian Robot each round, and
|
||||
// - there are at least 3 rounds left (1 to produce and buy an Obsidian Robot, 1 for that
|
||||
// to produce and buy a Geode Robot, and 1 for that to produce).
|
||||
// if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && inventory.clay_robots < self.obsidian_robot_clay_cost {
|
||||
if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && more_robots_required(inventory.clay_robots, inventory.clay, self.obsidian_robot_clay_cost, time_remaining-1) {
|
||||
let mut new_inventory = inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.clay_robot_cost;
|
||||
new_inventory.clay_robots += 1;
|
||||
open_set.push_back((new_inventory, time_remaining-1));
|
||||
}
|
||||
// Buy an Obsidian Robot if
|
||||
// - we can afford it, and
|
||||
// - we don't already produce enough Obsidian for a Geode Robot each round, and
|
||||
// - there are at least 2 rounds left (1 to produce and buy a Geode Robot, and 1 for that
|
||||
// to produce).
|
||||
// if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && inventory.obsidian_robots < self.geode_robot_obsidian_cost {
|
||||
if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && more_robots_required(inventory.obsidian_robots, inventory.obsidian, self.geode_robot_obsidian_cost, time_remaining-1) {
|
||||
let mut new_inventory = inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.obsidian_robot_ore_cost;
|
||||
new_inventory.clay -= self.obsidian_robot_clay_cost;
|
||||
new_inventory.obsidian_robots += 1;
|
||||
open_set.push_back((new_inventory, time_remaining-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Inventory {
|
||||
ore: u8,
|
||||
clay: u8,
|
||||
obsidian: u8,
|
||||
geodes: u8,
|
||||
ore_robots: u8,
|
||||
clay_robots: u8,
|
||||
obsidian_robots: u8,
|
||||
geode_robots: u8,
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
ore: 0,
|
||||
clay: 0,
|
||||
obsidian: 0,
|
||||
geodes: 0,
|
||||
ore_robots: 1,
|
||||
clay_robots: 0,
|
||||
obsidian_robots: 0,
|
||||
geode_robots: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(&mut self) {
|
||||
self.ore += self.ore_robots;
|
||||
self.clay += self.clay_robots;
|
||||
self.obsidian += self.obsidian_robots;
|
||||
self.geodes += self.geode_robots;
|
||||
}
|
||||
|
||||
fn as_arr(&self) -> [u8;8] {
|
||||
[
|
||||
self.ore,
|
||||
self.clay,
|
||||
self.obsidian,
|
||||
self.geodes,
|
||||
self.ore_robots,
|
||||
self.clay_robots,
|
||||
self.obsidian_robots,
|
||||
self.geode_robots,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(input: &str) -> Result<(usize, usize), ParseError> {
|
||||
let blueprints: Vec<Blueprint> = input.lines().map(Blueprint::try_from).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let first = blueprints.iter()
|
||||
.map(|blueprint| blueprint.id * blueprint.collect_geodes(24) as usize)
|
||||
.sum();
|
||||
|
||||
// let second = 0;
|
||||
let second = blueprints.iter()
|
||||
.take(3)
|
||||
.map(|blueprint| blueprint.collect_geodes(32) as usize)
|
||||
.product();
|
||||
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run(&sample_input), Ok((33, 3472)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run(&challenge_input), Ok((978, 15939)));
|
||||
}
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
use std::{fs, collections::HashMap};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Blueprint {
|
||||
id: usize,
|
||||
ore_robot_cost: u8,
|
||||
clay_robot_cost: u8,
|
||||
obsidian_robot_ore_cost: u8,
|
||||
obsidian_robot_clay_cost: u8,
|
||||
geode_robot_ore_cost: u8,
|
||||
geode_robot_obsidian_cost: u8,
|
||||
}
|
||||
|
||||
impl Blueprint {
|
||||
fn parse(line: &str) -> Self {
|
||||
let components: Vec<&str> = line.split(' ').collect();
|
||||
if components.len() != 32 {
|
||||
panic!("{line} does not have 32 components.");
|
||||
}
|
||||
Self {
|
||||
id: components[1][..components[1].len()-1].parse().unwrap(),
|
||||
ore_robot_cost: components[6].parse().unwrap(),
|
||||
clay_robot_cost: components[12].parse().unwrap(),
|
||||
obsidian_robot_ore_cost: components[18].parse().unwrap(),
|
||||
obsidian_robot_clay_cost: components[21].parse().unwrap(),
|
||||
geode_robot_ore_cost: components[27].parse().unwrap(),
|
||||
geode_robot_obsidian_cost: components[30].parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_geodes(&self, time: u8) -> u8 {
|
||||
let best = self.try_all(&Inventory::new(), time, &mut HashMap::new());
|
||||
println!("Best Result for Blueprint {} is {}", self.id, best);
|
||||
best
|
||||
}
|
||||
|
||||
fn try_all(&self, inventory: &Inventory, time_remaining: u8, mem: &mut HashMap<[u8;8], u8>) -> u8 {
|
||||
if let Some(best_time) = mem.get(&inventory.as_arr()) {
|
||||
if *best_time >= time_remaining && time_remaining > 1 {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
mem.insert(inventory.as_arr(), time_remaining);
|
||||
if time_remaining == 0 {
|
||||
return inventory.geodes;
|
||||
}
|
||||
let mut scores = Vec::new();
|
||||
// branch
|
||||
if time_remaining < 4 || inventory.ore < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() ||
|
||||
inventory.clay < self.obsidian_robot_clay_cost && inventory.clay_robots > 0 ||
|
||||
inventory.obsidian < self.geode_robot_obsidian_cost && inventory.obsidian_robots > 0
|
||||
{
|
||||
let mut new_inventory = *inventory;
|
||||
new_inventory.collect();
|
||||
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
|
||||
}
|
||||
if time_remaining > 2 && inventory.ore >= self.ore_robot_cost && inventory.ore_robots < *[self.ore_robot_cost, self.clay_robot_cost, self.obsidian_robot_ore_cost, self.geode_robot_ore_cost].iter().max().unwrap() {
|
||||
let mut new_inventory = *inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.ore_robot_cost;
|
||||
new_inventory.ore_robots += 1;
|
||||
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
|
||||
}
|
||||
if time_remaining > 3 && inventory.ore >= self.clay_robot_cost && inventory.clay_robots < self.obsidian_robot_clay_cost {
|
||||
let mut new_inventory = *inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.clay_robot_cost;
|
||||
new_inventory.clay_robots += 1;
|
||||
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
|
||||
}
|
||||
if time_remaining > 2 && inventory.ore >= self.obsidian_robot_ore_cost && inventory.clay >= self.obsidian_robot_clay_cost && inventory.obsidian_robots < self.geode_robot_obsidian_cost {
|
||||
let mut new_inventory = *inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.obsidian_robot_ore_cost;
|
||||
new_inventory.clay -= self.obsidian_robot_clay_cost;
|
||||
new_inventory.obsidian_robots += 1;
|
||||
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
|
||||
}
|
||||
if time_remaining > 1 && inventory.ore >= self.geode_robot_ore_cost && inventory.obsidian >= self.geode_robot_obsidian_cost {
|
||||
let mut new_inventory = *inventory;
|
||||
new_inventory.collect();
|
||||
new_inventory.ore -= self.geode_robot_ore_cost;
|
||||
new_inventory.obsidian -= self.geode_robot_obsidian_cost;
|
||||
new_inventory.geode_robots += 1;
|
||||
scores.push(self.try_all(&new_inventory, time_remaining-1, mem));
|
||||
}
|
||||
scores.iter().cloned().max().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Inventory {
|
||||
ore: u8,
|
||||
clay: u8,
|
||||
obsidian: u8,
|
||||
geodes: u8,
|
||||
ore_robots: u8,
|
||||
clay_robots: u8,
|
||||
obsidian_robots: u8,
|
||||
geode_robots: u8,
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
ore: 0,
|
||||
clay: 0,
|
||||
obsidian: 0,
|
||||
geodes: 0,
|
||||
ore_robots: 1,
|
||||
clay_robots: 0,
|
||||
obsidian_robots: 0,
|
||||
geode_robots: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(&mut self) {
|
||||
self.ore += self.ore_robots;
|
||||
self.clay += self.clay_robots;
|
||||
self.obsidian += self.obsidian_robots;
|
||||
self.geodes += self.geode_robots;
|
||||
}
|
||||
|
||||
fn as_arr(&self) -> [u8;8] {
|
||||
[
|
||||
self.ore,
|
||||
self.clay,
|
||||
self.obsidian,
|
||||
self.geodes,
|
||||
self.ore_robots,
|
||||
self.clay_robots,
|
||||
self.obsidian_robots,
|
||||
self.geode_robots,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn read_file(path: &str) -> String {
|
||||
fs::read_to_string(path)
|
||||
.expect("File not Found")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let blueprints: Vec<Blueprint> = read_file("input").lines().map(Blueprint::parse).collect();
|
||||
|
||||
let quality_level: usize = blueprints.iter()
|
||||
.map(|blueprint| blueprint.id * blueprint.collect_geodes(24) as usize)
|
||||
.sum();
|
||||
|
||||
println!("The sum of all of our quality levels is {quality_level}"); // should be 33 for the sample_input
|
||||
|
||||
let max_score: usize = blueprints.iter()
|
||||
.take(3)
|
||||
.map(|blueprint| blueprint.collect_geodes(32) as usize)
|
||||
.product();
|
||||
|
||||
println!("With added time, the remaining blueprints multiply to {max_score}.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sample_input() {
|
||||
let blueprints: Vec<_>= read_file("tests/sample_input").lines().map(Blueprint::parse).collect();
|
||||
|
||||
let quality_level: usize = blueprints.iter().map(|b| b.id * b.collect_geodes(24) as usize).sum();
|
||||
let max_score: usize = blueprints.iter().take(3).map(|b| b.collect_geodes(32) as usize).product();
|
||||
assert_eq!(quality_level, 33);
|
||||
assert_eq!(max_score, 62);
|
||||
}
|
||||
#[test]
|
||||
fn challenge_input() {
|
||||
let blueprints: Vec<_>= read_file("tests/input").lines().map(Blueprint::parse).collect();
|
||||
|
||||
let quality_level: usize = blueprints.iter().map(|b| b.id * b.collect_geodes(24) as usize).sum();
|
||||
let max_score: usize = blueprints.iter().take(3).map(|b| b.collect_geodes(32) as usize).product();
|
||||
assert_eq!(quality_level, 978);
|
||||
assert_eq!(max_score, 15939);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue