diff --git a/aoc2019/benches/bench.rs b/aoc2019/benches/bench.rs index 548f1e1..5d80aec 100644 --- a/aoc2019/benches/bench.rs +++ b/aoc2019/benches/bench.rs @@ -13,6 +13,7 @@ use aoc2019::day10; use aoc2019::day11; use aoc2019::day12; use aoc2019::day13; +use aoc2019::day14; fn aoc2019_all(c: &mut Criterion) { c.bench_function("day01", |b| b.iter(|| day01::run().unwrap())); @@ -28,6 +29,7 @@ fn aoc2019_all(c: &mut Criterion) { c.bench_function("day11", |b| b.iter(|| day11::run().unwrap())); c.bench_function("day12", |b| b.iter(|| day12::run().unwrap())); c.bench_function("day13", |b| b.iter(|| day13::run().unwrap())); + c.bench_function("day14", |b| b.iter(|| day14::run().unwrap())); } criterion_group! { diff --git a/aoc2019/input/day14.txt b/aoc2019/input/day14.txt new file mode 100644 index 0000000..29b32d4 --- /dev/null +++ b/aoc2019/input/day14.txt @@ -0,0 +1,62 @@ +4 BFNQL => 9 LMCRF +2 XGWNS, 7 TCRNC => 5 TPZCH +4 RKHMQ, 1 QHRG, 5 JDSNJ => 4 XGWNS +6 HWTBC, 4 XGWNS => 6 CWCD +1 BKPZH, 2 FLZX => 9 HWFQG +1 GDVD, 2 HTSW => 8 CNQW +2 RMDG => 9 RKHMQ +3 RTLHZ => 3 MSKWT +1 QLNHG, 1 RJHCP => 3 GRDJ +10 DLSD, 2 SWKHJ, 15 HTSW => 1 TCRNC +4 SWKHJ, 24 ZHDSD, 2 DLSD => 3 CPGJ +1 SWKHJ => 1 THJHK +129 ORE => 8 KLSMQ +3 SLNKW, 4 RTLHZ => 4 LPVGC +1 SLNKW => 5 RLGFX +2 QHRG, 1 SGMK => 8 RJHCP +9 RGKCF, 7 QHRG => 6 ZHDSD +8 XGWNS, 1 CPGJ => 2 QLNHG +2 MQFJF, 7 TBVH, 7 FZXS => 2 WZMRW +13 ZHDSD, 11 SLNKW, 18 RJHCP => 2 CZJR +1 CNQW, 5 GRDJ, 3 GDVD => 4 FLZX +129 ORE => 4 RHSHR +2 HWTBC, 2 JDSNJ => 8 QPBHG +1 BKPZH, 8 SWKHJ => 6 WSWBV +8 RJHCP, 7 FRGJK => 1 GSDT +6 QPBHG => 4 BKPZH +17 PCRQV, 6 BFNQL, 9 GSDT, 10 MQDHX, 1 ZHDSD, 1 GRDJ, 14 BRGXB, 3 RTLHZ => 8 CFGK +8 RMDG => 6 SGMK +3 CZJR => 8 RTLHZ +3 BFRTV => 7 RGKCF +6 FRGJK, 8 CZJR, 4 GRDJ => 4 BRGXB +4 VRVGB => 7 PCRQV +4 TCRNC, 1 TBVH, 2 FZXS, 1 BQGM, 1 THJHK, 19 RLGFX => 2 CRJTJ +5 RDNJK => 6 SWKHJ +2 FLVC, 2 SLNKW, 30 HWTBC => 8 DLSD +6 TBVH, 3 ZHDSD => 5 BQGM +17 RLGFX => 4 SCZQN +8 SWKHJ => 6 FZXS +9 LZHZ => 3 QDCL +2 ZHDSD => 1 RDNJK +15 FZXS, 3 TPZCH => 6 MQFJF +12 RLGFX, 9 QPBHG, 6 HTSW => 1 BFNQL +150 ORE => 9 BFRTV +2 BFRTV, 2 KLSMQ => 2 RMDG +4 VFLNM, 30 RKHMQ, 4 CRJTJ, 24 CFGK, 21 SCZQN, 4 BMGBG, 9 HWFQG, 34 CWCD, 7 LPVGC, 10 QDCL, 2 WSWBV, 2 WTZX => 1 FUEL +6 RHSHR, 3 RGKCF, 1 QHRG => 6 JDSNJ +3 MQDHX, 2 XGWNS, 12 GRDJ => 9 LZHZ +128 ORE => 6 ZBWLC +9 JDSNJ, 7 RMDG => 8 FLVC +4 DLSD, 12 CZJR, 3 MSKWT => 4 MQDHX +2 BXNX, 4 ZBWLC => 3 QHRG +19 LMCRF, 3 JDSNJ => 2 BMGBG +1 RJHCP, 26 SGMK => 9 HTSW +2 QPBHG => 8 VFLNM +2 RGKCF => 9 SLNKW +3 LZHZ, 2 GRDJ => 2 TBVH +100 ORE => 2 BXNX +4 DLSD, 21 JDSNJ => 8 GDVD +2 QHRG => 2 HWTBC +1 LPVGC, 8 XGWNS => 8 FRGJK +9 FZXS => 7 VRVGB +7 WZMRW, 1 TBVH, 1 VFLNM, 8 CNQW, 15 LZHZ, 25 PCRQV, 2 BRGXB => 4 WTZX diff --git a/aoc2019/src/day14.rs b/aoc2019/src/day14.rs new file mode 100644 index 0000000..d7d62c6 --- /dev/null +++ b/aoc2019/src/day14.rs @@ -0,0 +1,227 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::fmt::Write; + +use aoc::err; +use aoc::Result; + +const INPUT: &str = include_str!("../input/day14.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + writeln!(res, "part 2: {}", part2(INPUT)?)?; + + Ok(res) +} + +fn parse_recipes(input: &str) -> Result> { + let mut recipes = HashMap::new(); + + for line in input.lines() { + let arrow = line + .find(" => ") + .ok_or_else(|| err!("couldn't find arrow in line: {}", line))?; + + let elems = &line[..arrow]; + let elems = elems + .split(", ") + .map(|elem| { + let space = elem + .find(' ') + .ok_or_else(|| err!("couldn't find separator for elem {}", elem))?; + let amount = elem[..space].parse()?; + let name = &elem[(space + 1)..]; + + Ok(RecipeElem { + name: name.to_string(), + amount, + }) + }) + .collect::>>()?; + + let result = &line[(arrow + 4)..].trim_end(); + let space = result + .find(' ') + .ok_or_else(|| err!("couldn't find separator for result {}", result))?; + let result_amount = result[..space].parse()?; + let result_name = &result[(space + 1)..]; + + recipes.insert( + result_name.to_string(), + Recipe { + produced: result_amount, + elems, + }, + ); + } + + Ok(recipes) +} + +fn get_ore_cost( + material: String, + quantity: u64, + recipes: &HashMap, + inventory: &mut HashMap, +) -> Result { + if material == "ORE" { + return Ok(quantity); + } + + let mut total = 0; + + let mut in_stock = *inventory.entry(material.clone()).or_default(); + if in_stock < quantity { + let recipe = recipes + .get(&material) + .ok_or_else(|| err!("couldn't find recipe for {}", material))?; + + let needed = quantity - in_stock; + let num_reactions = (needed + recipe.produced - 1) / recipe.produced; + for elem in recipe.elems.iter() { + total += get_ore_cost( + elem.name.clone(), + elem.amount * num_reactions, + recipes, + inventory, + )?; + } + in_stock += num_reactions * recipe.produced; + } + + inventory.insert(material.clone(), in_stock - quantity); + + Ok(total) +} + +fn part1(input: &str) -> Result { + let recipes = parse_recipes(input)?; + let mut inventory = HashMap::new(); + + get_ore_cost("FUEL".to_string(), 1, &recipes, &mut inventory) +} + +fn part2(input: &str) -> Result { + let mut begin: u64 = 0; + let mut end: u64 = 1_000_000_000_000; + + let recipes = parse_recipes(input)?; + + while begin <= end { + let mid = begin + (end - begin) / 2; + let mut inventory = HashMap::new(); + + let ore_cost = get_ore_cost("FUEL".to_string(), mid, &recipes, &mut inventory)?; + match ore_cost.cmp(&1_000_000_000_000) { + Ordering::Greater => end = mid - 1, + Ordering::Less => begin = mid + 1, + Ordering::Equal => return Ok(mid), + } + } + + Ok(end) +} + +struct RecipeElem { + name: String, + amount: u64, +} + +struct Recipe { + produced: u64, + elems: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED1: &str = "10 ORE => 10 A +1 ORE => 1 B +7 A, 1 B => 1 C +7 A, 1 C => 1 D +7 A, 1 D => 1 E +7 A, 1 E => 1 FUEL +"; + + const PROVIDED2: &str = "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 +"; + + const PROVIDED3: &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 +"; + + const PROVIDED4: &str = "2 VPVL, 7 FWMGM, 2 CXFTF, 11 MNCFX => 1 STKFG +17 NVRVD, 3 JNWZP => 8 VPVL +53 STKFG, 6 MNCFX, 46 VJHF, 81 HVMC, 68 CXFTF, 25 GNMV => 1 FUEL +22 VJHF, 37 MNCFX => 5 FWMGM +139 ORE => 4 NVRVD +144 ORE => 7 JNWZP +5 MNCFX, 7 RFSQX, 2 FWMGM, 2 VPVL, 19 CXFTF => 3 HVMC +5 VJHF, 7 MNCFX, 9 VPVL, 37 CXFTF => 6 GNMV +145 ORE => 6 MNCFX +1 NVRVD => 8 CXFTF +1 VJHF, 6 MNCFX => 4 RFSQX +176 ORE => 6 VJHF +"; + + const PROVIDED5: &str = "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 +"; + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED1).unwrap(), 31); + assert_eq!(part1(PROVIDED2).unwrap(), 165); + assert_eq!(part1(PROVIDED3).unwrap(), 13312); + assert_eq!(part1(PROVIDED4).unwrap(), 180697); + assert_eq!(part1(PROVIDED5).unwrap(), 2210736); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 532506); + } + + #[test] + fn part2_provided() { + assert_eq!(part2(PROVIDED3).unwrap(), 82892753); + assert_eq!(part2(PROVIDED4).unwrap(), 5586022); + assert_eq!(part2(PROVIDED5).unwrap(), 460664); + } + + #[test] + fn part2_real() { + assert_eq!(part2(INPUT).unwrap(), 2595245); + } +} diff --git a/aoc2019/src/lib.rs b/aoc2019/src/lib.rs index 246ddd4..db3f19e 100644 --- a/aoc2019/src/lib.rs +++ b/aoc2019/src/lib.rs @@ -13,3 +13,4 @@ pub mod day10; pub mod day11; pub mod day12; pub mod day13; +pub mod day14; diff --git a/aoc2019/src/main.rs b/aoc2019/src/main.rs index e95b7ec..08abbb9 100644 --- a/aoc2019/src/main.rs +++ b/aoc2019/src/main.rs @@ -14,6 +14,7 @@ use aoc2019::day10; use aoc2019::day11; use aoc2019::day12; use aoc2019::day13; +use aoc2019::day14; fn main() -> Result<()> { let days: &[DayFunc] = &[ @@ -30,6 +31,7 @@ fn main() -> Result<()> { day11::run, day12::run, day13::run, + day14::run, ]; aoc::run(days)