Skip to content

Commit

Permalink
Year 2019 Day 14
Browse files Browse the repository at this point in the history
  • Loading branch information
maneatingape committed Sep 7, 2023
1 parent 7e295bb commit ffd8da7
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ pie
| 11 | [Space Police](https://adventofcode.com/2019/day/11) | [Source](src/year2019/day11.rs) | 470 |
| 12 | [The N-Body Problem](https://adventofcode.com/2019/day/12) | [Source](src/year2019/day12.rs) | 1024 |
| 13 | [Care Package](https://adventofcode.com/2019/day/13) | [Source](src/year2019/day13.rs) | 3492 |
| 14 | [Space Stoichiometry](https://adventofcode.com/2019/day/14) | [Source](src/year2019/day14.rs) | 17 |

## 2015

Expand Down
1 change: 1 addition & 0 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mod year2019 {
benchmark!(year2019, day11);
benchmark!(year2019, day12);
benchmark!(year2019, day13);
benchmark!(year2019, day14);
}

mod year2020 {
Expand Down
57 changes: 57 additions & 0 deletions input/year2019/day14.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
5 HLJD, 1 QHSZD, 13 SKZX => 8 MQPH
10 LSLV => 4 JNJHW
1 MQGF, 4 ZWXDQ, 1 GNSZ => 9 DGDH
1 SKZX, 3 DJSP => 1 MCHV
6 TWSR, 10 ZHDFS, 10 LQZXQ => 9 LXQNX
1 FRVW, 1 CJTW => 9 BRCB
20 ZHVNP => 8 XMXL
7 JQJXP => 1 ZGZDW
13 KRCM => 6 KXPQ
4 ZWXDQ, 4 KFKQF, 1 DZDX => 2 MQGF
8 DZDX, 2 ZKGM => 3 KFKQF
3 FXFTB => 8 KVDGP
10 MVGLF, 3 MWFBW, 13 XMXL, 1 CJTW, 2 ZSXJZ, 2 TNCZH, 3 MPFKN, 6 LXQNX => 2 MZMZQ
5 FRVW => 3 NWBTP
1 MVGLF, 2 NLXD, 6 KVDGP, 2 MQPH, 4 FXTJ, 10 TKXKF, 2 FRWV => 2 CSNS
13 TWSR => 9 BNWT
2 KRCM => 7 LSLV
1 ZHDFS, 11 NTVZD, 1 JQJXP => 6 ZHVNP
2 MCHV, 1 JNJHW => 6 NDQNH
32 SMHJH, 6 KXPQ => 1 CJTW
15 FXFTB, 1 MVGLF => 9 MPFKN
119 ORE => 9 KRCM
3 TNCZH => 9 BFQLT
5 MPFKN, 7 TKXKF, 6 JQJXP, 2 DZDX, 16 LCQJ, 4 DGDH, 4 ZGZDW => 7 WVXW
1 ZHDFS, 1 LXQNX => 3 TNCZH
4 ZMVKM, 1 BRQT => 3 QHSZD
24 FRVW, 1 KVDGP, 2 ZLNM => 3 FGLNK
2 KXPQ, 1 LSLV, 22 HNRQ => 5 ZWXDQ
6 ZWXDQ => 1 FRVW
1 FXFTB, 2 MWFBW => 6 ZHDFS
32 FRVW => 5 FRWV
6 FXFTB, 6 NDQNH, 2 MWFBW => 1 JQJXP
9 ZMVKM, 6 QHSZD, 5 LSLV => 4 SMHJH
3 CHKZ => 6 HLJD
21 BFQLT => 6 FXTJ
1 SMHJH, 4 FXFTB => 6 CHKZ
13 FRVW, 13 JQJXP, 1 GNSZ => 8 ZSXJZ
2 NDQNH => 8 NTVZD
3 KRCM => 2 ZKGM
13 ZHDFS, 14 ZWXDQ, 1 CHKZ => 7 LQZXQ
2 BNWT, 3 CHKZ => 7 ZLNM
167 ORE => 1 BRQT
1 LSLV => 3 DZDX
8 MZMZQ, 7 NWBTP, 3 WVXW, 44 MQPH, 3 DJSP, 1 CSNS, 3 BRCB, 32 LQZXQ => 1 FUEL
8 ZLNM => 2 NLXD
30 JQJXP, 9 FGLNK => 7 LCQJ
1 ZKGM, 19 KXPQ => 8 DJSP
4 DJSP => 6 FXFTB
25 NFTPZ => 6 ZMVKM
14 ZHVNP, 1 MVGLF => 9 TKXKF
1 BRQT => 2 SKZX
6 ZKGM => 7 HNRQ
3 DZDX => 5 TWSR
1 SMHJH => 7 MVGLF
3 NDQNH => 1 GNSZ
153 ORE => 9 NFTPZ
14 MCHV, 4 JNJHW, 2 DJSP => 4 MWFBW
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ pub mod year2019 {
pub mod day11;
pub mod day12;
pub mod day13;
pub mod day14;
}

/// # What could go wrong trying to enjoy a well deserved vacation?
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ fn all_solutions() -> Vec<Solution> {
solution!(year2019, day11),
solution!(year2019, day12),
solution!(year2019, day13),
solution!(year2019, day14),
// 2020
solution!(year2020, day01),
solution!(year2020, day02),
Expand Down
131 changes: 131 additions & 0 deletions src/year2019/day14.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! # Space Stoichiometry
//!
//! Sorting the reactions in [topological order](https://en.wikipedia.org/wiki/Topological_sorting)
//! from `FUEL` at the start to `ORE` at the end, allows us to process each reaction only once.
use crate::util::hash::*;
use crate::util::iter::*;
use crate::util::parse::*;
use std::cmp::Ordering;

struct Ingredient {
amount: u64,
chemical: usize,
}

pub struct Reaction {
amount: u64,
chemical: usize,
ingredients: Vec<Ingredient>,
}

/// To speed things up when processing, we use a temporary map to convert chemical names into
/// contiguous indices.
pub fn parse(input: &str) -> Vec<Reaction> {
let lines: Vec<_> = input.lines().collect();

let mut reactions: Vec<_> = (0..lines.len() + 1)
.map(|_| {
Reaction {
amount: 0,
chemical: 1, // Default to ORE, other chemicals will overwrite.
ingredients: Vec::new(),
}
})
.collect();

// Assign FUEL and ORE known indices as we'll need to look them up later.
let mut indices = FastMap::new();
indices.insert("FUEL", 0);
indices.insert("ORE", 1);

for line in lines {
let mut tokens = line
.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|s| !s.is_empty())
.rev()
.chunk::<2>();

// Assigns other indices in the arbitrary order that chemicals are encountered.
let [kind, amount] = tokens.next().unwrap();
let size = indices.len();
let chemical = *indices.entry(kind).or_insert(size);

let reaction = &mut reactions[chemical];
reaction.amount = amount.unsigned();
reaction.chemical = chemical;

for [kind, amount] in tokens {
let amount = amount.unsigned();
let size = indices.len();
let chemical = *indices.entry(kind).or_insert(size);
reaction.ingredients.push(Ingredient { amount, chemical });
}
}

// Sort reactions in topological order
let mut order = vec![0; reactions.len()];
topological(&reactions, &mut order, 0, 0);
reactions.sort_unstable_by_key(|r| order[r.chemical]);
reactions
}

/// Calculate the amount of ore needed for 1 fuel. This will be the most ore needed per unit of
/// fuel. Larger amounts of fuel can use some of the leftover chemicals from intermediate reactions.
pub fn part1(input: &[Reaction]) -> u64 {
ore(input, 1)
}

/// Find the maximum amount of fuel possible from 1 trillion ore with an efficient binary search.
pub fn part2(input: &[Reaction]) -> u64 {
let threshold = 1_000_000_000_000;
let mut start = 1;
let mut end = threshold;

while start < end {
let middle = (start + end) / 2;

match ore(input, middle).cmp(&threshold) {
Ordering::Less => start = middle + 1,
Ordering::Equal => return middle,
Ordering::Greater => end = middle - 1,
}
}

start
}

/// Sort reactions in topological order from FUEL at the root to ORE at the leaves. Reactions may
/// occur more than once at different depths in the graph, so we take the maximum depth.
fn topological(reactions: &[Reaction], order: &mut [usize], chemical: usize, depth: usize) {
order[chemical] = order[chemical].max(depth);

for ingredient in &reactions[chemical].ingredients {
topological(reactions, order, ingredient.chemical, depth + 1);
}
}

/// Run the reactions to find ore needed. Each chemical is processed only once, so we don't need
/// to track excess values of intermediate chemicals.
fn ore(reactions: &[Reaction], amount: u64) -> u64 {
let mut total = vec![0; reactions.len()];
total[0] = amount;

for reaction in &reactions[..reactions.len() - 1] {
let multiplier = div_ceil(total[reaction.chemical], reaction.amount);

for ingredient in &reaction.ingredients {
total[ingredient.chemical] += multiplier * ingredient.amount;
}
}

total[1]
}

/// Stable implementation of the nightly `div_ceil` method.
fn div_ceil(a: u64, b: u64) -> u64 {
if a % b == 0 {
a / b
} else {
(a / b) + 1
}
}
1 change: 1 addition & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mod year2019 {
mod day11_test;
mod day12_test;
mod day13_test;
mod day14_test;
}

mod year2020 {
Expand Down
24 changes: 24 additions & 0 deletions tests/year2019/day14_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use aoc::year2019::day14::*;

const EXAMPLE: &str = "\
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";

#[test]
fn part1_test() {
let input = parse(EXAMPLE);
assert_eq!(part1(&input), 13312);
}

#[test]
fn part2_test() {
let input = parse(EXAMPLE);
assert_eq!(part2(&input), 82892753);
}

0 comments on commit ffd8da7

Please sign in to comment.