diff --git a/Cargo.toml b/Cargo.toml index c59f6f7..c440e63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,10 @@ version = "0.1.0" authors = ["Stefan Schwarz "] edition = "2018" + +[profile.release] +debug = true + [[bin]] name = "day1" path = "src/day1/main.rs" @@ -43,3 +47,7 @@ path = "src/day11/main.rs" [[bin]] name = "day12" path = "src/day12/main.rs" + +[[bin]] +name = "day14" +path = "src/day14/main.rs" diff --git a/input14 b/input14 new file mode 100644 index 0000000..ea6fb95 --- /dev/null +++ b/input14 @@ -0,0 +1,56 @@ +18 FHWM => 1 XGQNZ +4 FVPWN, 9 CQGW => 7 QGHT +22 KZMS, 1 DMCJL => 8 TWGCK +1 LMGQN, 1 DSWDM, 1 GKGZ => 1 TGPH +22 WCSW => 1 LVTG +13 JSWR => 4 GKGZ +162 ORE => 3 FVPWN +59 CQGW, 15 MSNG, 6 XGKRF, 10 LJRQ, 1 HRKGV, 15 RKVC => 1 FUEL +5 DMCJL => 1 QBLH +2 XDRJ, 2 RKVC => 8 CTCNL +1 QXHX => 5 GFPSK +22 QGHT, 6 GFPSK, 5 DHTPL => 3 CSDR +4 QGHT, 2 HFXD => 4 XDRJ +10 WQCGV, 1 JSWR, 21 RHTLN => 7 VTPC +11 CQGW, 1 FVPWN => 3 HFXD +5 VTPC => 2 NCXW +8 LDZVS => 6 DQLH +117 ORE => 2 KWZNB +3 TGPH, 1 JPFQ, 2 WHWLK, 5 RKVC, 16 DQLH => 9 LJRQ +14 KWZNB, 2 CQGW => 8 MLPK +6 LDZVS => 2 JSWR +1 RKVC, 8 HCGT, 9 DHTPL => 6 FHWM +3 DHTPL, 1 HWSR, 36 LDZVS => 6 DSWDM +5 WHWLK, 1 LJHWT, 8 HSTHS => 7 VMPX +22 ZJCDZ, 3 WQCGV => 5 DHTPL +10 LJHWT, 32 GFPSK, 2 RHTLN => 4 HFRMP +2 FKVD, 3 TWGCK, 1 HWSR => 1 RNLZW +2 CSDR, 3 DQLH, 2 HSTHS => 9 JPFQ +1 JSWR, 1 PCWS, 1 HFRMP => 3 XGKRF +2 QGHT, 9 LVTG, 3 QBLH => 7 RHTLN +10 LJHWT, 4 CTCNL => 8 QXHX +16 MLPK, 1 HFXD => 9 ZJCDZ +6 QGHT => 9 WCSW +4 HWSR, 4 MLPK, 1 KZMS => 3 BGZHQ +12 MLPK => 8 RKVC +1 HWSR, 1 VNWFS => 7 BGFJ +7 FHWM, 11 CTDF, 1 LDZVS => 2 VNWFS +4 CTDF => 4 HSTHS +2 ZJCDZ => 6 LJHWT +1 VMPX, 1 NCXW, 1 HSTHS, 41 XGKRF, 30 HLNG, 1 GKGZ => 7 HRKGV +1 XGQNZ, 10 PCWS, 3 BGFJ => 8 FKVD +1 GFPSK, 1 DMCJL, 1 LVTG => 5 XDTZB +3 WCSW => 5 KZMS +6 TWGCK, 1 QXHX, 4 BGFJ => 2 LMGQN +1 WCSW => 7 LDZVS +1 XDTZB, 9 VNWFS => 3 WHWLK +3 HFXD, 4 WCSW, 1 MLPK => 5 WQCGV +2 BGFJ, 1 HSTHS, 22 MDCB, 10 HWSR, 6 RNLZW, 8 GKGZ => 5 MSNG +4 QGHT, 1 FKVD => 7 MDCB +9 MLPK, 3 LJHWT => 7 DMCJL +121 ORE => 2 CQGW +9 DHTPL, 2 BGZHQ => 8 CTDF +2 JSWR, 30 RHTLN => 7 HLNG +2 QBLH => 7 PCWS +14 LVTG => 8 HWSR +7 DMCJL => 1 HCGT diff --git a/src/day14/main.rs b/src/day14/main.rs new file mode 100644 index 0000000..6f22d81 --- /dev/null +++ b/src/day14/main.rs @@ -0,0 +1,383 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::convert::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +type Resource = String; +type Pipeline = HashMap; + +#[derive(Debug)] +struct ResourceBuilder { + requirements: Vec, + out_count: usize, +} + +impl From<(Vec, usize)> for ResourceBuilder { + fn from(build: (Vec, usize)) -> Self { + ResourceBuilder { + requirements: build.0, + out_count: build.1, + } + } +} + +#[derive(Default, Debug)] +struct NanoFactory { + pipeline: Pipeline, + ordered: Vec, +} + +#[derive(Debug)] +enum NanoFactoryBuildState { + InputCount, + InputResource, + OutputArrow, + OutputCount, + OutputResource, +} + +impl NanoFactory { + /// sort resources + fn resolve_order(&mut self) { + self.ordered = vec![Resource::from("ORE")]; + let mut sortable: HashMap> = self + .pipeline + .iter() + .map(|(k, v)| (k.clone(), v.requirements.clone())) + .collect(); + while sortable.len() > 0 { + sortable = sortable + .into_iter() + .filter_map(|(k, v)| { + if v.iter().all(|i| self.ordered.contains(i)) { + self.ordered.push(k); + None + } else { + Some((k, v)) + } + }) + .collect(); + } + self.ordered.reverse(); + self.ordered.pop(); + } + + /// calculate how much ores are required for required_fuel + fn resource_usage(&self, required_fuel: u128) -> usize { + // create storage to track leftover resources + let mut storage: HashMap = HashMap::new(); + storage.insert(Resource::from("FUEL"), required_fuel); + + // calculate resource costs + self.ordered.iter().for_each(|res| { + let rb = self.pipeline.get(res).unwrap(); + let count = (storage.get(res).unwrap().clone() + (rb.out_count as u128) - 1) + / (rb.out_count as u128); + rb.requirements.iter().for_each(|req| { + storage + .entry(req.clone()) + .and_modify(|v| *v += count) + .or_insert(count); + storage.insert(res.clone(), 0); + }) + }); + + // return cost for ore + *storage.get("ORE").unwrap() as usize + } + + /// generate a single fuel + fn generate_fuel(&self) -> usize { + let mut storage: HashMap = HashMap::new(); + self.lookup_resource(&mut storage, "FUEL") + } + + /// recursively calculate the cost of a resource in ores + fn lookup_resource(&self, storage: &mut HashMap, resource: &str) -> usize { + if resource == "ORE" { + return 1; + } + + // use from storage + if let Some(count) = storage.get_mut(resource) { + if *count > 0 { + *count -= 1; + return 0; + } + } + + // initialize storage with zero + if storage.get(resource).is_none() { + storage.insert(resource.into(), 0); + } + + // build resource and and to storage + let required = self + .pipeline + .get(resource) + .expect("unable to find resource"); + let cost = required.requirements.iter().fold(0, |mut acc, c| { + acc += self.lookup_resource(storage, c); + acc + }); + + // update storage + let stored = storage.get_mut(resource).unwrap(); + *stored += required.out_count - 1; + + cost + } +} + +impl From for NanoFactory +where + T: BufRead, +{ + fn from(bufreader: T) -> Self { + let mut f = NanoFactory::default(); + let mut state = NanoFactoryBuildState::InputCount; + bufreader.split(b'\n').enumerate().for_each(|(nr, line)| { + let line = line.unwrap(); + let mut line = line.into_iter().peekable(); + let mut count_var = 0; + let mut cur_requirements = Vec::new(); + loop { + match state { + // numeric input + NanoFactoryBuildState::InputCount | NanoFactoryBuildState::OutputCount => { + while let Some(c) = next_if_in_range(&mut line, 48..=57) { + count_var = count_var * 10 + (c as usize) - 48; + } + assert_eq!(line.next().unwrap(), b' '); + state = match state { + NanoFactoryBuildState::InputCount => { + NanoFactoryBuildState::InputResource + } + NanoFactoryBuildState::OutputCount => { + NanoFactoryBuildState::OutputResource + } + _ => unreachable!(), + } + } + // input resouce name + NanoFactoryBuildState::InputResource => { + let mut r = Resource::new(); + while let Some(c) = next_if_in_range(&mut line, 65..=90) { + r.push(c.into()); + } + for _ in 0..count_var { + cur_requirements.push(r.clone()); + } + r.truncate(0); + count_var = 0; + let next = line.next(); + match next { + Some(b',') => { + assert_eq!(line.next().unwrap(), b' '); + state = NanoFactoryBuildState::InputCount; + } + Some(b' ') => state = NanoFactoryBuildState::OutputArrow, + _ => panic!( + "invalid format in line {}, expected ' ' or ',', got '{:?}'", + nr, next + ), + } + } + // output resource name + NanoFactoryBuildState::OutputResource => { + let mut r = Resource::new(); + while let Some(c) = next_if_in_range(&mut line, 65..=90) { + r.push(c.into()); + } + f.pipeline + .insert(r, ResourceBuilder::from((cur_requirements, count_var))); + state = NanoFactoryBuildState::InputCount; + return; + } + // arrow between input and output ' => ' + NanoFactoryBuildState::OutputArrow => { + assert_eq!(line.next().unwrap(), b'='); + assert_eq!(line.next().unwrap(), b'>'); + assert_eq!(line.next().unwrap(), b' '); + state = NanoFactoryBuildState::OutputCount + } + }; + } + }); + f + } +} + +fn next_if_in_range( + peekable: &mut std::iter::Peekable, + range: std::ops::RangeInclusive, +) -> Option +where + I: std::iter::Iterator, + T: Copy + std::cmp::PartialOrd, +{ + if let Some(peeked) = peekable.peek() { + if range.contains(peeked) { + peekable.next() + } else { + None + } + } else { + None + } +} + +fn main() { + let f = File::open("input14").unwrap(); + let bufreader = BufReader::new(f); + let mut factory = NanoFactory::from(bufreader); + + // part1 + println!("ores: {}", factory.generate_fuel()); + + // part2 as binary search + factory.resolve_order(); + let mut min = 1; + let mut max = 1_000_000_000_000; + let mut fuel; + loop { + fuel = (min + max) / 2; + let ore = factory.resource_usage(fuel); + match 1_000_000_000_000.cmp(&ore) { + Ordering::Less => max = fuel - 1, + Ordering::Greater => { + min = fuel + 1; + if min > max { + break; + } + } + Ordering::Equal => { + break; + } + }; + } + println!("ammount: {}", fuel); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn example1() { + let bufreader = BufReader::new( + "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 +" + .trim() + .as_bytes(), + ); + let factory = NanoFactory::from(bufreader); + assert_eq!(factory.generate_fuel(), 31); + assert_eq!(factory.resource_usage(1), 31); + } + + #[test] + fn example2() { + let bufreader = BufReader::new( + " +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 +" + .trim() + .as_bytes(), + ); + let factory = NanoFactory::from(bufreader); + assert_eq!(factory.generate_fuel(), 165); + assert_eq!(factory.resource_usage(1), 165); + } + + #[test] + fn example3() { + let bufreader = BufReader::new( + " +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 +" + .trim() + .as_bytes(), + ); + let factory = NanoFactory::from(bufreader); + assert_eq!(factory.generate_fuel(), 13312); + assert_eq!(factory.generate_fuel_from_ores(1_000_000_000_000), 82892753); + } + + #[test] + fn example4() { + let bufreader = BufReader::new( + " +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 +" + .trim() + .as_bytes(), + ); + let factory = NanoFactory::from(bufreader); + assert_eq!(factory.generate_fuel(), 180697); + assert_eq!(factory.resource_usage(1), 180697); + // TODO assert_eq!(factory.generate_fuel_from_ores(1_000_000_000_000), 5586022); + } + + #[test] + fn example5() { + let bufreader = BufReader::new( + " +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 +" + .trim() + .as_bytes(), + ); + let factory = NanoFactory::from(bufreader); + assert_eq!(factory.generate_fuel(), 2210736); + assert_eq!(factory.resource_usage(1), 2210736); + // TODO assert_eq!(factory.generate_fuel_from_ores(1_000_000_000_000), 460664); + } +}