195 lines
5.0 KiB
Rust
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"));
|
|
}
|
|
} |