use nom::{ bytes::complete::tag, character::complete::{i64, multispace0, multispace1}, multi::separated_list1, sequence::{delimited, tuple}, IResult, Parser, }; /// This function assumes your input vector is sorted. /// It might break if this is not the case. /// We don't check if this is actually true, because that would take O(n) time, /// while we aim for this function to only take up O(log n) time. fn binary_search<'a, Item: Ord + Eq>( value_to_find: &Item, vector_to_find_it_in: &'a Vec, ) -> Option<&'a Item> { if vector_to_find_it_in.is_empty() { None } else { let mut index = vector_to_find_it_in.len() / 2; let mut lower = 0; let mut upper = vector_to_find_it_in.len() - 1; while upper != lower { if vector_to_find_it_in[index] <= *value_to_find { upper = index; } else { lower = index; } index = (upper - lower) / 2; } if vector_to_find_it_in[index] == *value_to_find { Some(&vector_to_find_it_in[index]) } else { None } } } fn give_map_parser<'a: 'b, 'b: 'a>( map_name: &'a str, ) -> impl FnMut(&'a str) -> IResult<&'b str, Vec<(i64, i64, i64)>> { delimited( tag(map_name), separated_list1( multispace1, tuple((i64, multispace1, i64, multispace1, i64)).map(|(a, _, b, _, c)| (a, b, c)), ), multispace0, ) } fn parse_input( input: &str, ) -> IResult< &str, ( Vec, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, ), > { let (input, result) = tuple(( delimited(tag("seeds: "), separated_list1(tag(" "), i64), multispace1), give_map_parser("seed-to-soil map:\n"), give_map_parser("soil-to-fertilizer map:\n"), give_map_parser("fertilizer-to-water map:\n"), give_map_parser("water-to-light map:\n"), give_map_parser("light-to-temperature map:\n"), give_map_parser("temperature-to-humidity map:\n"), give_map_parser("humidity-to-location map:\n"), ))(input)?; Ok((input, result)) } fn destination_to_source_mapping(source: i64, mappings: &Vec<(i64, i64, i64)>) -> i64 { mappings .iter() .filter(|&(_destination_start, source_start, range)| { source >= *source_start && source < *source_start + *range }) .next() .map(|(destination_start, source_start, _range)| { destination_start + (source - source_start) }) .unwrap_or(source) } fn solve_1( almanac: &( Vec, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, ), ) -> i64 { almanac .0 .iter() .map(|&seed| { let soil = destination_to_source_mapping(seed, &almanac.1); let fertilizer = destination_to_source_mapping(soil, &almanac.2); let water = destination_to_source_mapping(fertilizer, &almanac.3); let light = destination_to_source_mapping(water, &almanac.4); let temperature = destination_to_source_mapping(light, &almanac.5); let humidity = destination_to_source_mapping(temperature, &almanac.6); destination_to_source_mapping(humidity, &almanac.7) }) .min() .unwrap() } fn search_range( input_range: (i64, i64), mappings: &Vec<(i64, i64, i64)>, ) -> Option<(i64, i64, i64)> { if mappings.is_empty() { None } else { let mut index = mappings.len() / 2; let mut lower = 0; let mut upper = mappings.len() - 1; while upper != lower { if mappings[index].1 <= input_range.0 && mappings[index].1 + mappings[index].2 > input_range.0 { return Some(mappings[index]); } else if mappings[index].1 < input_range.0 { lower = index; } else { upper = index - 1; } index = (upper + lower).div_ceil(2); } if index >= mappings.len() - 1 && mappings[index].1 + mappings[index].2 < input_range.1 { None } else if mappings[index].1 == input_range.0 { mappings.get(index + 1).copied() } else { Some(mappings[index]) } } } /// We will assume the mappings are sorted on the source, so we can more quickly, using binary search, find /// the suitable ranges. This is relevant because of the large numbers involved. fn get_suitable_range(input_range: (i64, i64), mappings: &Vec<(i64, i64, i64)>) -> Vec<(i64, i64)> { let mut found_until = input_range.0; let mut result = vec![]; while found_until < input_range.1 { println!( "{} {:?} {:?}", found_until, mappings, search_range((found_until, input_range.1), mappings) ); if let Some((destination_start, source_start, range)) = search_range((found_until, input_range.1), mappings) { if source_start > found_until { println!("ding"); result.push((found_until, std::cmp::min(source_start, input_range.1))); found_until = std::cmp::min(source_start, input_range.1); } else { println!("dong"); let max_source = std::cmp::min(source_start + range, input_range.1); result.push(( found_until + (destination_start - source_start), destination_start + (max_source - source_start), )); found_until = max_source; } } else { println!("dang"); result.push((found_until, input_range.1)); found_until = input_range.1; } } result } fn get_suitable_ranges( input_ranges: &Vec<(i64, i64)>, mappings: &Vec<(i64, i64, i64)>, ) -> Vec<(i64, i64)> { let mut output_ranges_uncompressed = input_ranges .iter() .map(|&(range_start, range_length)| { get_suitable_range((range_start, range_start + range_length), mappings) }) .collect::>>() .into_iter() .flatten() .collect::>(); output_ranges_uncompressed.sort_unstable(); let (mut lower, mut upper) = output_ranges_uncompressed[0]; let mut output_ranges = vec![]; for (range_low, range_up) in output_ranges_uncompressed { if range_low <= upper { upper = range_up; } else { output_ranges.push((lower, upper)); lower = range_low; upper = range_up; } } output_ranges.push((lower, upper)); output_ranges = output_ranges .iter() .map(|&(range_start, range_end)| (range_start, range_end - range_start)) .collect::>(); output_ranges } fn solve_2( almanac: &( Vec, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, Vec<(i64, i64, i64)>, ), ) -> i64 { let mut lowest_location_numbers = vec![]; let mut seed_iter = almanac.0.iter(); let mut soil_almanac = almanac.1.iter().map(|&n| n).collect::>(); soil_almanac.sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut fertilizer_almanac = almanac.2.iter().map(|&n| n).collect::>(); fertilizer_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut water_almanac = almanac.3.iter().map(|&n| n).collect::>(); water_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut light_almanac = almanac.4.iter().map(|&n| n).collect::>(); light_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut temperature_almanac = almanac.5.iter().map(|&n| n).collect::>(); temperature_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut humidity_almanac = almanac.6.iter().map(|&n| n).collect::>(); humidity_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); let mut location_almanac = almanac.7.iter().map(|&n| n).collect::>(); location_almanac .sort_unstable_by(|&(_d1, source1, _r1), (_d2, source2, _r2)| source1.cmp(source2)); while let (Some(&lower_seed), Some(&range_length)) = (seed_iter.next(), seed_iter.next()) { println!("ding"); let seed_range = vec![(lower_seed, range_length)]; let soil_ranges = get_suitable_ranges(&seed_range, &soil_almanac); println!("soil"); let fertilizer_ranges = get_suitable_ranges(&soil_ranges, &fertilizer_almanac); println!("fertilizer"); let water_ranges = get_suitable_ranges(&fertilizer_ranges, &water_almanac); println!("water"); let light_ranges = get_suitable_ranges(&water_ranges, &light_almanac); println!("light"); let temperature_ranges = get_suitable_ranges(&light_ranges, &temperature_almanac); println!("temperature"); let humidity_ranges = get_suitable_ranges(&temperature_ranges, &humidity_almanac); println!("humidity"); lowest_location_numbers.push( get_suitable_ranges(&humidity_ranges, &location_almanac) .iter() .map(|&(range_min, _range_max)| range_min) .min() .unwrap(), ); } *lowest_location_numbers.iter().min().unwrap() } fn main() { println!("Hello, this is Patrick!"); let input_text = include_str!("../input.txt"); let (_, almanac) = parse_input(input_text).unwrap(); println!( "The lowest location number corresponding to the initial seed numbers is {}", solve_1(&almanac) ); println!( "The lowest location number for any of the initial seed ranges is {}", solve_2(&almanac) ); } #[cfg(test)] mod tests { use super::*; #[test] fn test_1() { let input_text = include_str!("../test_input.txt"); let (_, almanac) = parse_input(input_text).unwrap(); assert_eq!(solve_1(&almanac), 35); } #[test] fn test_2() { let input_text = include_str!("../test_input.txt"); let (_, almanac) = parse_input(input_text).unwrap(); assert_eq!(solve_2(&almanac), 46); } }