use item_mapping::{ItemMapping, MappedRange}; use seed_range::SeedRange; mod item_mapping; mod parsing; mod seed_range; type UnsignedNumber = u128; pub fn solve_task_1(input: &str) -> String { let (seeds, mapping_layers) = parsing::parse(input); let min = min_location_from_given_single_seeds(&seeds, &mapping_layers); min.to_string() } fn min_location_from_given_single_seeds( seeds: &[UnsignedNumber], layers: &[Vec], ) -> UnsignedNumber { let mut min = UnsignedNumber::MAX; for next_seed in seeds { let mut current_seed = *next_seed; for next_layer in layers { for next_mapping in next_layer { if let Some(found_mapping) = next_mapping.map_point(current_seed) { current_seed = found_mapping; break; } } } min = current_seed.min(min); } min } pub fn solve_task_2(input: &str) -> String { let (starting_from, mappings) = { let (single_seeds, mappings) = parsing::parse(input); let seed_ranges = combine_single_to_ranges_for_seeds(&single_seeds); (seed_ranges, mappings) }; let mut min = UnsignedNumber::MAX; for next_start in starting_from { let min_of_last_round = go_through_all_rounds_for_one_starting_seed(next_start, &mappings); min = min_of_last_round.min(min); } min.to_string() } fn go_through_all_rounds_for_one_starting_seed( starting_from: SeedRange, all_rounds: &[Vec], ) -> UnsignedNumber { let mut for_next_round: Vec = vec![starting_from]; for next_round in all_rounds { let mut add_to_next_round = Vec::new(); for next_seed_range in for_next_round { let new_ones = simulate_one_round_on_one_seed_range(next_seed_range, next_round); add_to_next_round.extend(new_ones); } for_next_round = add_to_next_round; } for_next_round .into_iter() .map(|seed_range| seed_range.start()) .min() .unwrap() } fn simulate_one_round_on_one_seed_range( seed_range: SeedRange, rounds_mapping: &[ItemMapping], ) -> Vec { let (mut to_map, mut mapped_ranges): (Vec, Vec) = (vec![seed_range], Vec::new()); while let Some(next_to_map) = to_map.pop() { let mut found_a_mapping = false; for next_mapping in rounds_mapping { match next_mapping.map_range(next_to_map) { MappedRange::Outside => (), MappedRange::Within(mapped) => { mapped_ranges.push(mapped); found_a_mapping = true; break; } MappedRange::PartiallyWithin { outside, mapped } => { mapped_ranges.push(mapped); to_map.push(outside); found_a_mapping = true; break; } MappedRange::FromBothSidePartiallyWithin { outside: (left, right), mapped, } => { mapped_ranges.push(mapped); to_map.push(left); to_map.push(right); found_a_mapping = true; break; } } } if !found_a_mapping { mapped_ranges.push(next_to_map); } } mapped_ranges } fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec { assert!(single_seeds.len() % 2 == 0); single_seeds .chunks(2) .map(|array| match array { [start, end] => { let start = *start; let range = end - 1; let end = start + range; SeedRange::new(start, end) } _ => unreachable!(), }) .collect() } #[cfg(test)] mod testing { use std::str::FromStr; use crate::day5::{ combine_single_to_ranges_for_seeds, item_mapping::ItemMapping, parsing, seed_range::SeedRange, simulate_one_round_on_one_seed_range, solve_task_2, }; use super::solve_task_1; const EXAMPLE_INPUT: &str = include_str!("day5/day_example_input.txt"); #[test] fn should_solve_task_1() { let actual = solve_task_1(EXAMPLE_INPUT); let expected = "35"; assert_eq!(expected, actual) } #[test] fn should_combine_single_to_ranges_for_seeds() { let (single_seeds, _) = parsing::parse(EXAMPLE_INPUT); let actual = combine_single_to_ranges_for_seeds(&single_seeds); let expected = vec![SeedRange::new(79, 92), SeedRange::new(55, 67)]; assert_eq!(expected, actual) } #[test] fn should_solve_task2() { let actual = solve_task_2(EXAMPLE_INPUT); let expected = "46"; assert_eq!(expected, actual) } fn create_first_round_of_example() -> Vec { vec![ ItemMapping::from_str("50 98 2").unwrap(), ItemMapping::from_str("52 50 48").unwrap(), ] } #[test] fn should_simulate_one_round_on_one_seed_range() { fn assert_case( seed_range: SeedRange, rounds_mapping: &[ItemMapping], mut expected: Vec, ) { let mut actual = simulate_one_round_on_one_seed_range(seed_range, rounds_mapping); actual.sort(); expected.sort(); assert_eq!( expected, actual, "Given range: {:?}\nGiven mapping: {:?}", seed_range, rounds_mapping ); } let example = create_first_round_of_example(); assert_case(SeedRange::new(0, 4), &example, vec![SeedRange::new(0, 4)]); assert_case( SeedRange::new(100, 110), &example, vec![SeedRange::new(100, 110)], ); assert_case( SeedRange::new(98, 99), &example, vec![SeedRange::new(50, 51)], ); assert_case( SeedRange::new(98, 110), &example, vec![SeedRange::new(50, 51), SeedRange::new(100, 110)], ); assert_case( SeedRange::new(90, 110), &example, vec![ SeedRange::new(92, 99), SeedRange::new(50, 51), SeedRange::new(100, 110), ], ); assert_case( SeedRange::new(70, 98), &example, vec![SeedRange::new(72, 99), SeedRange::new(50, 50)], ); assert_case( SeedRange::new(40, 110), &example, vec![ SeedRange::new(40, 49), SeedRange::new(52, 99), SeedRange::new(50, 51), SeedRange::new(100, 110), ], ); } }