use itertools::Itertools; use ranges::{GenericRange, Ranges}; use rayon::prelude::*; use std::{ ops::{Bound, Range, RangeBounds}, str::Lines, }; fn main() { println!("{}", part2(include_str!("./input.txt"))); } #[derive(Debug)] struct MapEntry { destination: u64, source: u64, range_length: u64, } impl MapEntry { fn new(destination: u64, source: u64, range_length: u64) -> Self { Self { destination, source, range_length, } } fn source_range(&self) -> Range { self.source..(self.source + self.range_length) } fn get_offset(&self) -> i64 { self.destination as i64 - self.source as i64 } } fn part2(input: &str) -> u64 { let mut seeds = Vec::new(); let mut seed_to_soil = Vec::new(); let mut soil_to_fertilizer = Vec::new(); let mut fertilizer_to_water = Vec::new(); let mut water_to_light = Vec::new(); let mut light_to_temperature = Vec::new(); let mut temperature_to_humidity = Vec::new(); let mut humidity_to_location = Vec::new(); input.split("\n\n").for_each(|part| { let mut lines = part.lines(); let mut split = lines.next().unwrap().split(':'); let key = split.next().unwrap(); match key { "seeds" => split .next() .unwrap() .split_ascii_whitespace() .tuples() .for_each(|(s, l)| { let start = s.parse::().unwrap(); let length = l.parse::().unwrap(); seeds.push(start..(start + length)); }), "seed-to-soil map" => process_map(lines, &mut seed_to_soil), "soil-to-fertilizer map" => process_map(lines, &mut soil_to_fertilizer), "fertilizer-to-water map" => process_map(lines, &mut fertilizer_to_water), "water-to-light map" => process_map(lines, &mut water_to_light), "light-to-temperature map" => process_map(lines, &mut light_to_temperature), "temperature-to-humidity map" => process_map(lines, &mut temperature_to_humidity), "humidity-to-location map" => process_map(lines, &mut humidity_to_location), key => unreachable!("Invalid key: {}", key), } }); let maps = [ seed_to_soil, soil_to_fertilizer, fertilizer_to_water, water_to_light, light_to_temperature, temperature_to_humidity, humidity_to_location, ]; let seeds = seeds.into_iter().fold(Ranges::new(), |acc, r| acc.union(r)); find_min_location(seeds, &maps) } fn process_map(lines: Lines, map: &mut Vec) { lines .map(|l| { let parts = l.split_ascii_whitespace().collect::>(); MapEntry::new( parts[0].parse::().unwrap(), parts[1].parse::().unwrap(), parts[2].parse::().unwrap(), ) }) .for_each(|entry| { map.push(entry); }) } fn find_min_location(seeds: Ranges, maps: &[Vec; 7]) -> u64 { let mut new_ranges = seeds; for map in maps { new_ranges = apply_map(new_ranges, map); } new_ranges .as_slice() .par_iter() .map(|r| r.into_iter().min().unwrap()) .min() .unwrap() } fn apply_map(mut seeds: Ranges, map: &[MapEntry]) -> Ranges { let mut new_ranges = Ranges::new(); for entry in map { let matching_ranges_for_entry = seeds.clone().intersect(Ranges::from(entry.source_range())); seeds = seeds.difference(matching_ranges_for_entry.clone()); let offset = entry.get_offset(); let offset_ranges = offset_ranges(matching_ranges_for_entry, offset); new_ranges = new_ranges.union(offset_ranges); } new_ranges.union(seeds) } fn offset_ranges(ranges: Ranges, offset: i64) -> Ranges { ranges .as_slice() .iter() .map(|r| offset_range(*r, offset)) .collect::>() } fn offset_range(range: GenericRange, offset: i64) -> GenericRange { let range_start = if let Bound::Included(start) = range.start_bound() { *start as i128 + offset as i128 } else { panic!("should only be called with included start bound") } as u64; let range_end = if let Bound::Excluded(end) = range.end_bound() { *end as i128 + offset as i128 } else { panic!("should only be called with included start bound") } as u64; (range_start..range_end).into() } #[cfg(test)] mod tests { use super::*; const INPUT: &str = "seeds: 79 14 55 13\n\nseed-to-soil map:\n50 98 2\n52 50 48\n\nsoil-to-fertilizer map:\n0 15 37\n37 52 2\n39 0 15\n\nfertilizer-to-water map:\n49 53 8\n0 11 42\n42 0 7\n57 7 4\n\nwater-to-light map:\n88 18 7\n18 25 70\n\nlight-to-temperature map:\n45 77 23\n81 45 19\n68 64 13\n\ntemperature-to-humidity map:\n0 69 1\n1 0 69\n\nhumidity-to-location map:\n60 56 37\n56 93 4"; #[test] fn test_part2() { assert_eq!(part2(INPUT), 46); } }