diff --git a/5/Cargo.lock b/5/Cargo.lock index 5a6ff7f..5861f03 100644 --- a/5/Cargo.lock +++ b/5/Cargo.lock @@ -14,45 +14,13 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - [[package]] name = "day5" version = "0.1.0" dependencies = [ "itertools", - "rayon", + "num", + "test-case", ] [[package]] @@ -71,36 +39,145 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.9.0" +name = "num" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] -name = "rayon" -version = "1.8.0" +name = "proc-macro2" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ - "either", - "rayon-core", + "unicode-ident", ] [[package]] -name = "rayon-core" -version = "1.12.0" +name = "quote" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "proc-macro2", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "syn" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/5/Cargo.toml b/5/Cargo.toml index 6e81dde..c3f201a 100644 --- a/5/Cargo.toml +++ b/5/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" [dependencies] itertools = "0.12.0" -rayon = "1.8.0" +num = "0.4.1" +test-case = "3.3.1" diff --git a/5/src/main.rs b/5/src/main.rs index 95684f4..64bf3a8 100644 --- a/5/src/main.rs +++ b/5/src/main.rs @@ -153,16 +153,16 @@ fn main() { #[derive(Debug)] struct AlmanacMapping { - source_range: Range, - dest_range: Range, + source_range: Range, + dest_range: Range, } impl From<&str> for AlmanacMapping { fn from(s: &str) -> Self { let s_nums: Vec<_> = s.split_whitespace().take(3).collect(); - let length: u64 = s_nums[2].parse().unwrap(); - let source_start: u64 = s_nums[1].parse().unwrap(); - let dest_start: u64 = s_nums[0].parse().unwrap(); + let length: i64 = s_nums[2].parse().unwrap(); + let source_start: i64 = s_nums[1].parse().unwrap(); + let dest_start: i64 = s_nums[0].parse().unwrap(); AlmanacMapping { source_range: source_start..source_start + length, dest_range: dest_start..dest_start + length, @@ -171,7 +171,7 @@ impl From<&str> for AlmanacMapping { } impl AlmanacMapping { - fn lookup(&self, key: u64) -> Option { + fn lookup(&self, key: i64) -> Option { if self.source_range.contains(&key) { Some(self.dest_range.start + (key - self.source_range.start)) } else { @@ -186,13 +186,74 @@ struct AlmanacMappingTable { mappings: Vec, } +fn range_overlap(r1: &Range, r2: &Range) -> Option> { + let new_start = std::cmp::max(r1.start, r2.start); + let new_end = std::cmp::min(r1.end - T::one(), r2.end - T::one()); + + if new_start <= std::cmp::min(r1.end - T::one(), r2.end - T::one()) + && new_end >= std::cmp::max(r1.start, r2.start) + { + Some(new_start..new_end + T::one()) + } else { + None + } +} + +fn range_exclude(keep: &Range, exclude: &Range) -> Vec> { + let mut residual = Vec::new(); + if let Some(overlap) = range_overlap(keep, exclude) { + if keep.start < overlap.start { + residual.push(keep.start..overlap.start); + } + if keep.end > overlap.end { + residual.push(overlap.end..keep.end); + } + } else { + residual.push(keep.clone()); + } + residual +} + impl AlmanacMappingTable { - fn lookup(&self, key: u64) -> u64 { + fn lookup(&self, key: i64) -> i64 { self.mappings .iter() .find_map(|map| map.lookup(key)) .unwrap_or(key) } + fn lookup_ranges(&self, ranges: Vec>) -> Vec> { + ranges + .iter() + .flat_map(|range| self.lookup_range(range)) + .collect() + } + fn lookup_range(&self, range: &Range) -> Vec> { + let mut residual = vec![range.clone()]; + let mut resolved_mappings = Vec::new(); + + for map in &self.mappings { + let mut local_residual = Vec::new(); + for range in residual.into_iter() { + if let Some(overlap) = range_overlap(&range, &map.source_range) { + let dest_start_idx = overlap.start - map.source_range.start; + let length = overlap.end.saturating_sub(overlap.start); + resolved_mappings.push( + (map.dest_range.start + dest_start_idx) + ..(map.dest_range.start + dest_start_idx + length), + ); + local_residual.extend_from_slice(&range_exclude(&range, &overlap)); + } else { + local_residual.push(range); + } + } + residual = local_residual; + } + + // unresolved maps map 1:1 + resolved_mappings.extend_from_slice(&residual); + + resolved_mappings + } } impl From<&mut Lines> for AlmanacMappingTable { @@ -218,7 +279,7 @@ impl From<&mut Lines> for AlmanacMappingTable { #[derive(Debug)] struct Almanac { - seeds: Vec, + seeds: Vec, seed_to_soil: AlmanacMappingTable, soil_to_fertilizer: AlmanacMappingTable, fertilizer_to_water: AlmanacMappingTable, @@ -231,7 +292,7 @@ struct Almanac { impl From> for Almanac { fn from(mut lines: Lines) -> Self { let seeds_s = lines.next().unwrap().unwrap(); - let seeds: Vec = seeds_s + let seeds: Vec = seeds_s .split_once(": ") .unwrap() .1 @@ -254,7 +315,7 @@ impl From> for Almanac { } impl Almanac { - fn lookup(&self, seed_id: u64) -> u64 { + fn lookup(&self, seed_id: i64) -> i64 { self.humidity_to_location.lookup( self.temperature_to_humidity.lookup( self.light_to_temperature.lookup( @@ -268,6 +329,20 @@ impl Almanac { ), ) } + fn lookup_range(&self, range: Range) -> Vec> { + self.humidity_to_location.lookup_ranges( + self.temperature_to_humidity.lookup_ranges( + self.light_to_temperature.lookup_ranges( + self.water_to_light.lookup_ranges( + self.fertilizer_to_water.lookup_ranges( + self.soil_to_fertilizer + .lookup_ranges(self.seed_to_soil.lookup_ranges(vec![range])), + ), + ), + ), + ), + ) + } } // PROBLEM 1 solution @@ -287,7 +362,7 @@ impl Almanac { // What is the lowest location number that corresponds to any of the initial seed numbers? -fn problem1_lowest_location(almanac: &Almanac) -> u64 { +fn problem1_lowest_location(almanac: &Almanac) -> i64 { almanac .seeds .iter() @@ -296,7 +371,7 @@ fn problem1_lowest_location(almanac: &Almanac) -> u64 { .unwrap() } -fn problem1(input: Lines) -> u64 { +fn problem1(input: Lines) -> i64 { let almanac = Almanac::from(input); problem1_lowest_location(&almanac) } @@ -327,33 +402,40 @@ fn problem1(input: Lines) -> u64 { // the almanac. What is the lowest location number that corresponds to any of the // initial seed numbers? -fn problem2_lowest_location(almanac: &Almanac) -> u64 { +fn problem2_lowest_location(almanac: &Almanac) -> i64 { let seed_ranges = almanac .seeds .iter() .tuples() .map(|(range_start, length)| *range_start..*range_start + *length); - seed_ranges - .map(|range| { - range - .into_par_iter() - .map(|x| almanac.lookup(x)) - .min() - .unwrap() - }) - .min() - .unwrap() + // seed_ranges + // .map(|range| { + // range + // .into_par_iter() + // .map(|x| almanac.lookup(x)) + // .min() + // .unwrap() + // }) + // .min() + // .unwrap() + let ranges = seed_ranges + .flat_map(|range| almanac.lookup_range(range)) + .collect_vec(); + + ranges.iter().map(|range| range.start).min().unwrap() } -fn problem2(input: Lines) -> u64 { +fn problem2(input: Lines) -> i64 { let almanac = Almanac::from(input); problem2_lowest_location(&almanac) } #[cfg(test)] mod tests { + use num::Num; use std::io::{BufRead, Cursor}; + use test_case::test_case; use crate::*; @@ -413,6 +495,18 @@ humidity-to-location map: assert_eq!(a.lookup(79), 82); } + #[test_case(0..5, 2..4, Some(2..4) ; "one range overlaps completely")] + #[test_case(0..5, 2..10, Some(2..5) ; "one range overlaps from below")] + #[test_case(0..5, 5..10, None ; "no overlap, touching")] + #[test_case(0..5, 6..10, None ; "no overlap, disjoint")] + fn test_range_overlap( + r1: Range, + r2: Range, + expected: Option>, + ) { + assert_eq!(range_overlap(&r1, &r2), expected); + } + #[test] fn test_problem1_example() { let c = Cursor::new(EXAMPLE_ALMANAC);