add day 12+13+14

This commit is contained in:
Stefan Bühler 2019-12-14 13:04:29 +01:00
parent ff54ef107a
commit 4de7ad785e
11 changed files with 651 additions and 0 deletions

43
Cargo.lock generated
View File

@ -1,5 +1,10 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.40"
@ -48,6 +53,24 @@ dependencies = [
"shared 0.1.0",
]
[[package]]
name = "day12"
version = "0.1.0"
dependencies = [
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "day13"
version = "0.1.0"
dependencies = [
"shared 0.1.0",
]
[[package]]
name = "day14"
version = "0.1.0"
[[package]]
name = "day2"
version = "0.1.0"
@ -131,6 +154,23 @@ name = "libc"
version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-integer"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.6"
@ -186,6 +226,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
@ -195,6 +236,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"

View File

@ -11,5 +11,8 @@ members = [
"day9",
"day10",
"day11",
"day12",
"day13",
"day14",
"shared",
]

10
day12/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day12"
version = "0.1.0"
authors = ["Stefan Bühler <stbuehler@web.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
num-integer = "0.1.41"

4
day12/src/input.txt Normal file
View File

@ -0,0 +1,4 @@
<x=-2, y=9, z=-5>
<x=16, y=19, z=9>
<x=0, y=3, z=6>
<x=11, y=0, z=11>

166
day12/src/main.rs Normal file
View File

@ -0,0 +1,166 @@
fn strip_prefix<'a>(prefix: &str, input: &'a str) -> &'a str {
assert!(input.starts_with(prefix));
&input[prefix.len()..]
}
fn strip_suffix<'a>(suffix: &str, input: &'a str) -> &'a str {
assert!(input.ends_with(suffix));
&input[..input.len() - suffix.len()]
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Position {
pub x: i32,
pub y: i32,
pub z: i32,
}
impl Position {
pub fn parse(input: &str) -> Self {
let input = strip_suffix(">", strip_prefix("<", input.trim()));
let mut parts = input.split(",");
let x = strip_prefix("x=", parts.next().expect("need x component").trim()).parse().unwrap();
let y = strip_prefix("y=", parts.next().expect("need y component").trim()).parse().unwrap();
let z = strip_prefix("z=", parts.next().expect("need z component").trim()).parse().unwrap();
assert!(parts.next().is_none(), "unexpected data");
Position { x, y, z }
}
pub fn potential_energy(self) -> u32 {
self.x.abs() as u32 + self.y.abs() as u32 + self.z.abs() as u32
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Velocity {
pub dx: i32,
pub dy: i32,
pub dz: i32,
}
impl Velocity {
pub fn kinetic_energy(self) -> u32 {
self.dx.abs() as u32 + self.dy.abs() as u32 + self.dz.abs() as u32
}
}
impl std::ops::AddAssign<Velocity> for Position {
fn add_assign(&mut self, rhs: Velocity) {
self.x += rhs.dx;
self.y += rhs.dy;
self.z += rhs.dz;
}
}
fn parse_moons(input: &str) -> Vec<(Position, Velocity)> {
input.lines().map(Position::parse).map(|p| (p, Velocity::default())).collect()
}
fn step(moons: &mut [(Position, Velocity)]) {
for i in 0..moons.len() - 1 {
// change velocity based on "gravity"
for j in i + 1..moons.len() {
let dx = (moons[j].0.x - moons[i].0.x).signum();
moons[i].1.dx += dx; moons[j].1.dx -= dx;
let dy = (moons[j].0.y - moons[i].0.y).signum();
moons[i].1.dy += dy; moons[j].1.dy -= dy;
let dz = (moons[j].0.z - moons[i].0.z).signum();
moons[i].1.dz += dz; moons[j].1.dz -= dz;
}
}
for moon in moons {
moon.0 += moon.1;
}
}
fn energy(moons: &[(Position, Velocity)]) -> u32 {
moons.iter().map(|(p, v)| p.potential_energy() * v.kinetic_energy()).sum()
}
fn energy_after_steps(mut moons: Vec<(Position, Velocity)>, steps: u32) -> u32 {
for _ in 0..steps {
step(&mut moons);
}
energy(&moons)
}
type ExtractedAxis = Vec<(i32, i32)>;
fn extract_x(moons: &[(Position, Velocity)]) -> ExtractedAxis {
moons.iter().map(|(p, v)| (p.x, v.dx)).collect()
}
fn extract_y(moons: &[(Position, Velocity)]) -> ExtractedAxis {
moons.iter().map(|(p, v)| (p.y, v.dy)).collect()
}
fn extract_z(moons: &[(Position, Velocity)]) -> ExtractedAxis {
moons.iter().map(|(p, v)| (p.z, v.dz)).collect()
}
fn repeats_after_steps(mut moons: Vec<(Position, Velocity)>) -> u64 {
// as `step` is reversible the first repeated state will always be the initial state
// find cycle per axis
let mut result_x = None;
let initial_x = extract_x(&moons);
let mut result_y = None;
let initial_y = extract_y(&moons);
let mut result_z = None;
let initial_z = extract_z(&moons);
for current in 1u64.. {
step(&mut moons);
if result_x.is_none() && initial_x == extract_x(&moons) {
result_x = Some(current);
}
if result_y.is_none() && initial_y == extract_y(&moons) {
result_y = Some(current);
}
if result_z.is_none() && initial_z == extract_z(&moons) {
result_z = Some(current);
}
if let (Some(x), Some(y), Some(z)) = (result_x, result_y, result_z) {
return num_integer::lcm(num_integer::lcm(x, y), z);
}
}
unreachable!()
}
fn main() {
let moons = parse_moons(include_str!("input.txt"));
let energy = energy_after_steps(moons.clone(), 1000);
println!("Energy after 1000 steps: {}", energy);
println!("Repeats initial state after {} steps", repeats_after_steps(moons));
}
#[cfg(test)]
mod day12test {
use super::*;
#[test]
fn examples1() {
assert_eq!(179, energy_after_steps(parse_moons(
"<x=-1, y=0, z=2>
<x=2, y=-10, z=-7>
<x=4, y=-8, z=8>
<x=3, y=5, z=-1>"), 10));
assert_eq!(1940, energy_after_steps(parse_moons(
"<x=-8, y=-10, z=0>
<x=5, y=5, z=10>
<x=2, y=-7, z=3>
<x=9, y=-8, z=-3>
"), 100));
}
#[test]
fn examples2() {
assert_eq!(2772, repeats_after_steps(parse_moons(
"<x=-1, y=0, z=2>
<x=2, y=-10, z=-7>
<x=4, y=-8, z=8>
<x=3, y=5, z=-1>")));
assert_eq!(4686774924, repeats_after_steps(parse_moons(
"<x=-8, y=-10, z=0>
<x=5, y=5, z=10>
<x=2, y=-7, z=3>
<x=9, y=-8, z=-3>")));
}
}

10
day13/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "day13"
version = "0.1.0"
authors = ["Stefan Bühler <stbuehler@web.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
shared = { path = "../shared" }

1
day13/src/input.txt Normal file

File diff suppressed because one or more lines are too long

147
day13/src/main.rs Normal file
View File

@ -0,0 +1,147 @@
use shared::intcode::{IntCode, SimulateStep, CELL};
use std::collections::HashSet;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Tile {
Empty,
Wall,
Block,
HorizontalPaddle,
Ball,
}
impl Tile {
pub fn new(t: CELL) -> Self {
match t {
0 => Self::Empty,
1 => Self::Wall,
2 => Self::Block,
3 => Self::HorizontalPaddle,
4 => Self::Ball,
_ => panic!("Unknown tile {}", t),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Position {
pub x: CELL,
pub y: CELL,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Update {
Tile {
position: Position,
tile: Tile,
},
Score {
score: CELL,
},
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(i8)]
pub enum Joystick {
Left = -1,
Neutral = 0,
Right = 1,
}
fn view_game(mut ic: IntCode) -> impl Iterator<Item=(Position, Tile)> {
std::iter::from_fn(move || {
let x = match ic.simulate_step() {
SimulateStep::Finished => return None,
SimulateStep::WaitForInput => panic!("no input for game"),
SimulateStep::Output(n) => n,
};
let y = ic.simulate_step().expect_output();
let p = Position { x, y };
let tile = Tile::new(ic.simulate_step().expect_output());
Some((p, tile))
})
}
fn find_blocks<I>(tiles: I) -> HashSet<Position>
where
I: Iterator<Item=(Position, Tile)>,
{
let mut blocks = HashSet::new();
for (pos, tile) in tiles {
match tile {
Tile::Block => { blocks.insert(pos); }
_ => { blocks.remove(&pos); }
}
}
blocks
}
fn get_update(ic: &mut IntCode, joystick: Joystick) -> Option<Update> {
let joystick = joystick as i8 as CELL;
ic.next_input = Some(joystick);
let x = match ic.simulate_step() {
SimulateStep::Finished => {
ic.next_input = None;
return None;
},
SimulateStep::WaitForInput => panic!("no input for game"),
SimulateStep::Output(n) => n,
};
let y = ic.simulate_step().expect_output();
if x == -1 && y == 0 {
Some(Update::Score { score: ic.simulate_step().expect_output() })
} else {
let position = Position { x, y };
let tile = Tile::new(ic.simulate_step().expect_output());
Some(Update::Tile { position, tile })
}
}
fn break_all_blocks(mut ic: IntCode) -> CELL {
ic.data[0] = 2; // "play for free"
let mut last_score = None;
let mut last_paddle = None;
let mut last_ball = None;
let mut blocks = HashSet::new();
let mut had_blocks = false;
let mut joystick = Joystick::Neutral;
while let Some(update) = get_update(&mut ic, joystick) {
match update {
Update::Score { score } => last_score = Some(score),
Update::Tile { position, tile } => {
blocks.remove(&position);
match tile {
Tile::Empty => (),
Tile::Wall => (),
Tile::Block => { blocks.insert(position); had_blocks = true; },
Tile::HorizontalPaddle => last_paddle = Some(position),
Tile::Ball => last_ball = Some(position),
}
}
}
if let (Some(paddle), Some(ball)) = (&last_paddle, &last_ball) {
let diff_x = ball.x - paddle.x;
joystick = if diff_x > 0 {
Joystick::Right
} else if diff_x < 0 {
Joystick::Left
} else {
Joystick::Neutral
};
}
}
if had_blocks && blocks.is_empty() {
println!("Won game, all blocks destroyed");
} else {
println!("Lost game");
}
last_score.expect("no score written")
}
fn main() {
let ic = include_str!("input.txt").parse::<IntCode>().unwrap();
let blocks = find_blocks(view_game(ic.clone()));
println!("Number of blocks: {}", blocks.len());
let score = break_all_blocks(ic);
println!("Score: {}", score);
}

9
day14/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "day14"
version = "0.1.0"
authors = ["Stefan Bühler <stbuehler@web.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

63
day14/src/input.txt Normal file
View File

@ -0,0 +1,63 @@
12 VJWQR, 1 QTBC => 6 BGXJV
12 BGTMN, 2 DRKQR => 2 TVSF
2 FTFK => 2 THNDN
13 LKRTN, 7 MDPN, 12 NZKQZ => 5 LPWPD
1 HDKX, 3 DWZS, 1 RCBQS => 1 DCRK
14 ZCMF => 6 ZKHLC
3 ZFVH, 2 ZCMF, 1 SCJG, 1 LQWJ, 4 BGBJ, 1 NHPR, 3 VKZFJ, 7 FWFXZ => 4 QVJMP
11 TNMLB => 7 NVDCR
1 LPWPD, 1 BGBJ => 2 SCJG
3 DFCVF, 1 QGSN => 6 PQXG
1 BGXJV, 1 THNDN => 4 BCQN
3 LKRTN => 9 MDPN
2 THNDN, 13 RCKZ, 10 FQSLN => 8 VKZFJ
4 LBCZ, 9 LWHS => 1 FQSLN
6 WSRVZ => 9 TNMLB
8 FQSLN, 14 JQRF, 4 BGTMN => 5 QGSN
4 ZCMF, 4 PLSM, 2 ZHTX => 8 TDHPM
2 RSKC, 10 SHBC, 8 MDPN => 6 FMSZ
2 VJWQR => 2 FPTV
12 DRKQR => 6 NHPR
35 QJLF, 22 BGTMN, 11 VJWTR, 1 QVJMP, 8 LQWJ, 1 TWLC, 16 NXZCH, 18 THKF, 42 JBLM => 1 FUEL
2 BGTMN, 4 XJKN => 8 ZCMF
4 TVSF => 3 RSKC
7 HRWS, 1 TVSF => 3 ZHTX
134 ORE => 4 WSRVZ
1 VKZFJ, 1 TWLC, 4 ZHTX, 5 THNDN, 12 PLVN, 1 ZFXNP, 1 PQXG, 6 CWHV => 7 VJWTR
20 XJKN, 1 LCKW, 3 NZKQZ => 7 HDKX
1 LPWPD => 8 RCKZ
4 RCBQS, 1 NVDCR => 5 BGBJ
8 BGXJV => 4 BGTMN
13 QBDX, 16 BGXJV => 6 NZKQZ
2 LPWPD => 3 DRKQR
4 QBDX => 7 XJKN
12 LCKW, 9 NVDCR => 3 RCBQS
142 ORE => 3 QBDX
1 WXHJF => 1 XKDJ
2 RSKC => 2 CWHV
2 ZHTX, 1 ZFXNP => 6 JQRF
1 FTFK, 1 TVSF, 1 QBDX => 2 JBLM
1 TDHPM, 14 NHPR, 3 QPSF => 5 ZFVH
3 GDTPC, 1 ZKHLC => 8 ZFXNP
5 DWZS => 3 LQWJ
1 FTFK, 4 LBCZ, 13 NHPR => 1 FWFXZ
1 RCBQS, 12 SHBC => 9 FTFK
1 WSRVZ, 1 XKDJ => 5 LKRTN
2 BGTMN, 1 MDPN => 5 PLSM
2 BGXJV, 17 XKDJ, 4 FPTV => 9 LCKW
148 ORE => 2 QTBC
110 ORE => 2 VJWQR
42 ZFXNP, 15 RCKZ, 8 GDTPC => 3 QJLF
13 HRWS => 4 GDTPC
34 HRWS => 4 DFCVF
2 VKZFJ, 2 NHPR, 16 PLVN, 1 QPSF, 13 LBCZ, 4 DCRK, 10 LWHS => 7 NXZCH
3 CWHV, 1 THNDN => 7 LWHS
1 BGXJV, 2 QBDX => 5 DWZS
9 LQWJ => 8 QPSF
21 BCQN, 3 FMSZ, 1 RSKC => 5 THKF
118 ORE => 6 WXHJF
11 FMSZ => 9 TWLC
28 PLSM => 5 SHBC
1 ZKHLC, 23 SCJG => 7 LBCZ
17 DWZS, 16 THNDN => 9 PLVN
7 HDKX => 9 HRWS

195
day14/src/main.rs Normal file
View File

@ -0,0 +1,195 @@
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"));
}
}