aoc2019/day14/src/main.rs

195 lines
5.0 KiB
Rust

use std::collections::HashMap;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ChemicalAmount<'a> {
pub amount: u64,
pub name: &'a str,
}
impl<'a> ChemicalAmount<'a> {
pub fn parse(input: &'a str) -> Self {
let input = input.trim();
let mut elems = input.split_whitespace();
let amount = elems.next().expect("need amount").trim().parse::<u64>().expect("number for amount");
let name = elems.next().expect("need amount").trim();
assert!(elems.next().is_none(), "unexpected data");
ChemicalAmount {
amount,
name,
}
}
}
pub type Inputs<'a> = Vec<ChemicalAmount<'a>>;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Reaction<'a> {
pub inputs: Inputs<'a>,
pub output: ChemicalAmount<'a>,
}
impl<'a> Reaction<'a> {
pub fn parse(input: &'a str) -> Self {
let mut parts = input.split("=>");
let inputs = parts.next().expect("need inputs").trim().split(",").map(ChemicalAmount::parse).collect();
let output = ChemicalAmount::parse(parts.next().expect("need output").trim());
assert!(parts.next().is_none(), "unexpected data");
Reaction {
inputs,
output,
}
}
pub fn runs_for(&self, required_output: u64) -> u64 {
(required_output + self.output.amount - 1) / self.output.amount
}
}
#[derive(Clone, Debug)]
pub struct Reactions<'a>(pub HashMap<&'a str, Reaction<'a>>);
impl<'a> Reactions<'a> {
pub fn parse(input: &'a str) -> Self {
Self(input.lines().map(Reaction::parse).map(|reaction| (reaction.output.name.into(), reaction)).collect())
}
fn gather_ore(&self, needs: &mut HashMap<&'a str, u64>, name: &'a str, additional: u64) -> u64 {
if name == "ORE" { return additional; }
let previous_need;
let updated_need;
{
let need = needs.entry(&name).or_default();
previous_need = *need;
updated_need = previous_need + additional;
*need = updated_need;
}
let reaction = &self.0[name];
let previous_runs = reaction.runs_for(previous_need);
let updated_runs = reaction.runs_for(updated_need);
let mut ore = 0;
if updated_runs > previous_runs {
let new_runs = updated_runs - previous_runs;
for input in &reaction.inputs {
ore += self.gather_ore(needs, input.name, input.amount * new_runs);
}
}
ore
}
fn ore_for_fuel(&self, fuel: u64) -> u64 {
let mut needs = HashMap::new();
self.gather_ore(&mut needs, "FUEL", fuel)
}
}
// check must be monotone, there must be a `n` such that for all `i`: `i <= n <=> check(i)`
// returns `None` if this `n` is less than 0 (i.e. check is never true)
fn binary_search_max<F>(check: F, start: u64, max_step: u64) -> Option<u64>
where
F: Fn(u64) -> bool,
{
let mut upper;
let mut lower;
// find boundaries
if !check(start) {
// find lower limit
if start == 0 { return None; }
upper = start - 1;
loop {
lower = start / 2;
if check(lower) { break; }
if lower == 0 { return None; }
upper = lower - 1;
}
} else {
lower = start;
// find upper limit
upper = start;
loop {
upper += max_step;
if !check(upper) {
upper -= 1;
break;
}
}
}
// invariant: check(lower) && !check(upper + 1)
while lower != upper {
debug_assert!(lower < upper);
let mid = (lower + upper + 1) / 2;
debug_assert!(mid > lower);
if check(mid) {
lower = mid;
} else {
upper = mid - 1;
}
}
Some(lower)
}
fn main() {
let reactions = Reactions::parse(include_str!("input.txt"));
let ore_one_fuel;
{
ore_one_fuel = reactions.ore_for_fuel(1);
println!("Need {} ORE for one FUEL", ore_one_fuel);
}
let have_ore = 1000000000000u64;
let max_fuel = binary_search_max(|fuel| {
reactions.ore_for_fuel(fuel) <= have_ore
}, have_ore / ore_one_fuel, have_ore / ore_one_fuel).unwrap();
println!("Can get {} fuel for {} ore", max_fuel, have_ore);
}
#[cfg(test)]
mod day14test {
use super::*;
fn calculate_needed_ore(input: &str) -> u64 {
let reactions = Reactions::parse(input);
reactions.ore_for_fuel(1)
}
#[test]
fn examples1() {
assert_eq!(165, calculate_needed_ore(
"9 ORE => 2 A
8 ORE => 3 B
7 ORE => 5 C
3 A, 4 B => 1 AB
5 B, 7 C => 1 BC
4 C, 1 A => 1 CA
2 AB, 3 BC, 4 CA => 1 FUEL"));
assert_eq!(13312, calculate_needed_ore(
"157 ORE => 5 NZVS
165 ORE => 6 DCFZ
44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL
12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ
179 ORE => 7 PSHF
177 ORE => 5 HKGWZ
7 DCFZ, 7 PSHF => 2 XJWVT
165 ORE => 2 GPVTF
3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT"));
assert_eq!(2210736, calculate_needed_ore(
"171 ORE => 8 CNZTR
7 ZLQW, 3 BMBT, 9 XCVML, 26 XMNCP, 1 WPTQ, 2 MZWV, 1 RJRHP => 4 PLWSL
114 ORE => 4 BHXH
14 VRPVC => 6 BMBT
6 BHXH, 18 KTJDG, 12 WPTQ, 7 PLWSL, 31 FHTLT, 37 ZDVW => 1 FUEL
6 WPTQ, 2 BMBT, 8 ZLQW, 18 KTJDG, 1 XMNCP, 6 MZWV, 1 RJRHP => 6 FHTLT
15 XDBXC, 2 LTCX, 1 VRPVC => 6 ZLQW
13 WPTQ, 10 LTCX, 3 RJRHP, 14 XMNCP, 2 MZWV, 1 ZLQW => 1 ZDVW
5 BMBT => 4 WPTQ
189 ORE => 9 KTJDG
1 MZWV, 17 XDBXC, 3 XCVML => 2 XMNCP
12 VRPVC, 27 CNZTR => 2 XDBXC
15 KTJDG, 12 BHXH => 5 XCVML
3 BHXH, 2 VRPVC => 7 MZWV
121 ORE => 7 VRPVC
7 XCVML => 6 RJRHP
5 BHXH, 4 VRPVC => 5 LTCX"));
}
}