use std::{collections::HashMap, ops::Range}; use regex::Regex; pub fn run() { let input = include_str!("../input/day5.txt"); // let input = r#"seeds: 79 14 55 13 // seed-to-soil map: // 50 98 2 // 52 50 48 // soil-to-fertilizer map: // 0 15 37 // 37 52 2 // 39 0 15 // fertilizer-to-water map: // 49 53 8 // 0 11 42 // 42 0 7 // 57 7 4 // water-to-light map: // 88 18 7 // 18 25 70 // light-to-temperature map: // 45 77 23 // 81 45 19 // 68 64 13 // temperature-to-humidity map: // 0 69 1 // 1 0 69 // humidity-to-location map: // 60 56 37 // 56 93 4"#; let almanac = Almanac::parse_seed_list(input); let part1 = almanac.convert_seeds_to_locations(); dbg!(part1.iter().min().unwrap()); let almanac2 = Almanac::parse_seed_range(input); let part2 = almanac2.convert_seeds_to_locations(); dbg!(part2.iter().min().unwrap()); } #[derive(Debug, Clone)] struct Almanac + Clone> { seeds: S, maps: HashMap<(String, String), Map>, } fn parse_maps<'a>(parts: impl Iterator) -> HashMap<(String, String), Map> { let mut maps = HashMap::new(); let map_name_re = Regex::new(r#"(\w+)-to-(\w+) map:"#).unwrap(); for map_s in parts { let (name, rest) = map_s.split_once("\n").unwrap(); let map_captures = map_name_re.captures(name).unwrap(); let map = Map::parse(rest); maps.insert( (map_captures[1].to_owned(), map_captures[2].to_owned()), map, ); } maps } impl Almanac> { fn parse_seed_list(s: &str) -> Self { let mut parts = s.split("\n\n"); let seeds_s = parts.next().unwrap(); assert!(seeds_s.starts_with("seeds: ")); let seeds_s = seeds_s.split_at("seeds: ".len()).1; let seeds = seeds_s .split(" ") .map(|s| s.parse::().unwrap()) .collect::>(); Self { seeds, maps: parse_maps(parts), } } } #[derive(Debug, Clone, PartialEq)] struct Seeds { ranges: Vec>, cur: Option>, } impl Iterator for Seeds { type Item = i64; fn next(&mut self) -> Option { if self.cur.is_none() || self.cur.as_ref().unwrap().is_empty() { if self.ranges.is_empty() { self.cur = None; return None; } else { self.cur = Some(self.ranges.remove(0)); return self.next(); } } else { let cur = self.cur.as_ref().unwrap(); let ret = cur.start; self.cur = Some((cur.start + 1)..cur.end); return Some(ret); } } } impl Almanac { fn parse_seed_range(s: &str) -> Self { let mut parts = s.split("\n\n"); let seeds_s = parts.next().unwrap(); assert!(seeds_s.starts_with("seeds: ")); let seeds_s = seeds_s.split_at("seeds: ".len()).1; let numbers = seeds_s .split(" ") .map(|s| s.parse::().unwrap()) .collect::>(); assert!(numbers.len() % 2 == 0); let ranges = numbers .chunks(2) .map(|chunk| chunk[0]..(chunk[0] + chunk[1])) .collect::>(); Self { seeds: Seeds { ranges, cur: None }, maps: parse_maps(parts), } } } impl + Clone> Almanac { fn convert(&self, xs: &mut Vec, from: &str, to: &str) { let map = self .maps .get(&(from.to_owned(), to.to_owned())) .expect(&format!("conversion from {} to {}", from, to)); for i in 0..xs.len() { xs[i] = map.convert(xs[i]); } } fn convert_seeds_to_locations(&self) -> Vec { let path = &[ "soil", "fertilizer", "water", "light", "temperature", "humidity", "location", ]; let mut cur_type = "seed"; let mut cur = self.seeds.clone().into_iter().collect::>(); for next in path { self.convert(&mut cur, cur_type, next); cur_type = next; } cur } } #[derive(Debug, Clone, PartialEq)] struct Map { // range of source and offset to dest ranges: Vec<(Range, i64)>, } impl Map { fn parse(s: &str) -> Self { let ranges = s .lines() .map(|l| { let mut parts = l.trim().split(" "); let dest_start: i64 = parts.next().unwrap().parse().unwrap(); let source_start: i64 = parts.next().unwrap().parse().unwrap(); let len: i64 = parts.next().unwrap().parse().unwrap(); assert_eq!(parts.next(), None); ( source_start..(source_start + len), dest_start - source_start, ) }) .collect::>(); Self { ranges } } fn convert(&self, source: i64) -> i64 { for (range, offset) in self.ranges.iter() { if range.contains(&source) { return source + offset; } } source } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_map() { assert_eq!( Map::parse("50 98 2\n52 50 48"), Map { ranges: vec![(98..100, -48), (50..98, 2),], } ) } #[test] fn test_map_convert() { let m = Map { ranges: vec![(98..100, -48), (50..98, 2)], }; assert_eq!(m.convert(98), 50); assert_eq!(m.convert(53), 55); assert_eq!(m.convert(10), 10); } #[test] fn test_parse_almanac() { let a = Almanac::parse_seed_list( r#"seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48"#, ); assert_eq!(a.seeds, vec![79, 14, 55, 13]); let k = ("seed".to_owned(), "soil".to_owned()); assert_eq!(a.maps.keys().collect::>(), vec![&k]); assert_eq!( a.maps[&k], Map { ranges: vec![(98..100, -48), (50..98, 2)], } ); } #[test] fn test_parse_almanac_seed_ranges() { let a = Almanac::parse_seed_range("seeds: 1 2 3 4"); assert_eq!( a.seeds, Seeds { ranges: vec![1..3, 3..7], cur: None, } ); } #[test] fn test_seeds_iter() { assert_eq!( Seeds { ranges: vec![1..3, 3..7], cur: None, } .collect::>(), vec![1, 2, 3, 4, 5, 6] ); } }