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::().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>; #[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(check: F, start: u64, max_step: u64) -> Option 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")); } }