diff --git a/3 b/3 deleted file mode 100644 index e69de29..0000000 diff --git a/src/solutions/day5.rs b/src/solutions/day5.rs index 6e63db7..76e507d 100644 --- a/src/solutions/day5.rs +++ b/src/solutions/day5.rs @@ -1,4 +1,4 @@ -use item_mapping::ItemMapping; +use item_mapping::{ItemMapping, MappedRange}; use seed_range::SeedRange; mod item_mapping; @@ -41,7 +41,78 @@ pub fn solve_task_2(input: &str) -> String { let seed_ranges = combine_single_to_ranges_for_seeds(&single_seeds); (seed_ranges, mappings) }; - "".to_string() + 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 { @@ -63,8 +134,11 @@ fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec 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), + ], + ); + } } diff --git a/src/solutions/day5/item_mapping.rs b/src/solutions/day5/item_mapping.rs index 1067f77..20dcab9 100644 --- a/src/solutions/day5/item_mapping.rs +++ b/src/solutions/day5/item_mapping.rs @@ -1,10 +1,10 @@ -use std::str::FromStr; +use std::{str::FromStr}; use thiserror::Error; use super::{seed_range::SeedRange, UnsignedNumber}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct ItemMapping { source_range: SeedRange, target: UnsignedNumber, @@ -12,13 +12,84 @@ pub struct ItemMapping { } impl ItemMapping { + + fn offset_mapping(&self, to_map: UnsignedNumber) -> UnsignedNumber { + assert!(to_map >= self.source_range.start()); + let difference = to_map - self.source_range.start(); + self.target + difference + } + + pub fn map_range(&self, to_map: SeedRange) -> MappedRange { + let start = self.source_range.start(); + let end = self.source_range.end(); + // xxxxxx => to_map + // yyyyyy => self + + // -------xxxxxx + // yyyyyy------- + if to_map.start() > end || + // xxxxxxx--------- + // ----------yyyyyy + to_map.end() < start { + MappedRange::Outside + } else if to_map.start() < start && to_map.end() <= end { + // xxxxxxx---- + // ----yyyyyyy + let outside_start = to_map.start(); + let outside_end = start.saturating_sub(1); + let outside = SeedRange::new(outside_start, outside_end); + + let mapped_start = self.target; + let mapped_end = self.offset_mapping(to_map.end()); + let mapped = SeedRange::new(mapped_start, mapped_end); + MappedRange::PartiallyWithin { outside, mapped } + } else if to_map.start() >= start && to_map.end() > end { + // yyyyyy---- + // ----xxxxxx + let outside_start = end + 1; + let outside_end = to_map.end(); + let outside = SeedRange::new(outside_start, outside_end); + + let mapped_start = self.offset_mapping(to_map.start()); + let mapped_end = self.offset_mapping(outside_start - 1); + let mapped = SeedRange::new(mapped_start, mapped_end); + MappedRange::PartiallyWithin { outside, mapped } + + } else if to_map.start() < start && to_map.end() > end { + + // -----yyy---- + // ----xxxxxx + let left = { + let left_start = to_map.start(); + let left_end = start - 1; + SeedRange::new(left_start, left_end) + }; + let right = { + let right_start = end + 1; + let right_end = to_map.end(); + SeedRange::new(right_start, right_end) + }; + let mapped = { + let mapped_start = self.target; + let mapped_end = self.offset_mapping(end); + SeedRange::new(mapped_start, mapped_end) + }; + MappedRange::FromBothSidePartiallyWithin { outside: (left, right), mapped } + } else { + // ----yyyyyy---- + // -----xxxx----- + let mapped_start = self.offset_mapping(to_map.start()); + let mapped_end = self.offset_mapping(to_map.end()); + let mapped = SeedRange::new(mapped_start, mapped_end); + MappedRange::Within(mapped) + } + } + pub fn map_point(&self, to_map: UnsignedNumber) -> Option { let source_range = self.source_range; let start = source_range.start(); - let end = source_range.end(); - if start <= to_map && end >= to_map { - let difference = to_map - start; - let mapped = self.target + difference; + let end = source_range.end(); if start <= to_map && end >= to_map { + let mapped = self.offset_mapping(to_map); Some(mapped) } else { None @@ -26,6 +97,20 @@ impl ItemMapping { } } +#[derive(Debug, PartialEq, Eq)] +pub enum MappedRange { + Outside, + Within(SeedRange), + PartiallyWithin { + outside: SeedRange, + mapped: SeedRange, + }, + FromBothSidePartiallyWithin { + outside: (SeedRange, SeedRange), + mapped: SeedRange, + }, +} + #[derive(Debug, Error, PartialEq, Eq)] #[error("Item mapping must be exactly 3 numbers")] pub struct InvalidStrItemMapping; @@ -59,7 +144,7 @@ impl FromStr for ItemMapping { #[cfg(test)] mod testing { use crate::solutions::day5::{ - item_mapping::{InvalidStrItemMapping, ItemMapping}, + item_mapping::{InvalidStrItemMapping, ItemMapping, MappedRange}, seed_range::SeedRange, UnsignedNumber, }; @@ -115,4 +200,74 @@ mod testing { assert_case(4, "4 2 2", None); assert_case(79, "52 50 48", Some(81)); } + + #[test] + fn should_map_ranges() { + fn assert_case(given: SeedRange, mapping: ItemMapping, expected: MappedRange) { + let actual = mapping.map_range(given); + assert_eq!( + expected, actual, + "Given: {:?}\nMapping: {:?}", + given, mapping + ); + } + let example_mapping = ItemMapping { + source_range: SeedRange::new(98, 99), + target: 50, + range: 2, + }; + + assert_case( + SeedRange::new(95, 97), + example_mapping.clone(), + MappedRange::Outside, + ); + assert_case( + SeedRange::new(100, 120), + example_mapping.clone(), + MappedRange::Outside, + ); + assert_case( + SeedRange::new(98, 99), + example_mapping.clone(), + MappedRange::Within(SeedRange::new(50, 51)), + ); + let greater_example_mapping = ItemMapping { + source_range: SeedRange::new(50, 97), + target: 52, + range: 48, + }; + assert_case( + SeedRange::new(40, 80), + greater_example_mapping.clone(), + MappedRange::PartiallyWithin { + outside: SeedRange::new(40, 49), + mapped: SeedRange::new(52, 82), + }, + ); + assert_case( + SeedRange::new(80, 100), + greater_example_mapping.clone(), + MappedRange::PartiallyWithin { + outside: SeedRange::new(98, 100), + mapped: SeedRange::new(82, 99), + }, + ); + assert_case( + SeedRange::new(30, 100), + greater_example_mapping.clone(), + MappedRange::FromBothSidePartiallyWithin { + outside: (SeedRange::new(30, 49), SeedRange::new(98, 100)), + mapped: SeedRange::new(52, 99), + }, + ); + assert_case( + SeedRange::new(96, 102), + example_mapping.clone(), + MappedRange::FromBothSidePartiallyWithin { + outside: (SeedRange::new(96, 97), SeedRange::new(100, 102)), + mapped: SeedRange::new(50, 51), + }, + ); + } } diff --git a/src/solutions/day5/seed_range.rs b/src/solutions/day5/seed_range.rs index b62fbc7..2c2e0fb 100644 --- a/src/solutions/day5/seed_range.rs +++ b/src/solutions/day5/seed_range.rs @@ -1,6 +1,6 @@ use super::UnsignedNumber; -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] pub struct SeedRange { start: UnsignedNumber, end: UnsignedNumber, @@ -8,6 +8,7 @@ pub struct SeedRange { impl SeedRange { pub fn new(start: UnsignedNumber, end: UnsignedNumber) -> Self { + assert!(end >= start); Self { start, end } } diff --git a/src/solutions/day5/task2.rs b/src/solutions/day5/task2.rs deleted file mode 100644 index 4adc8d4..0000000 --- a/src/solutions/day5/task2.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::collections::HashMap; - -use crate::{ - iterations::{AdventIterator, Chunck}, - solutions::day5::parsing, -}; - -use super::{ - mapping_layer::MappingLayer, - range_mapping::{MappedSeedRange, RangeMapping}, - seed_range::SeedRange, - UnsignedNumber, -}; - -pub fn solve_task_2(input: &str) -> String { - let (seeds, layers) = parsing::parse(input); - let seed_ranges = combine_seeds_to_ranges(seeds); - let mut min = UnsignedNumber::MAX; - for next in seed_ranges { - let loacal_min = simuluate_one_seed_range(next, &layers); - min = min.min(loacal_min); - } - min.to_string() -} - -fn simuluate_one_seed_range(seed_range: SeedRange, layers: &[MappingLayer]) -> UnsignedNumber { - let mut current_branches = vec![seed_range]; - - for next in layers { - let mut cache = CacheOfMappedRanges::new(); - let ranges = next.ranges(); - #[cfg(debug_assertions)] - { - let mut sorted: Vec = current_branches - .iter() - .copied() - .map(|e| e.start()) - .collect(); - let unsorted = sorted.clone(); - sorted.sort(); - assert_eq!(unsorted, sorted); - } - - for next_seed_range in current_branches { - map_sequence(next_seed_range, ranges, &mut cache); - } - - current_branches = cache - .into_iter() - .map(|(start, end)| SeedRange::new(start, end)) - .collect(); - current_branches.sort_by_key(|not_the_key| not_the_key.start()); - } - - let min_location = current_branches - .into_iter() - .min_by_key(|seed_range| seed_range.start()) - .unwrap(); - min_location.start() -} - -type CacheOfMappedRanges = HashMap; -fn map_sequence( - seed_range: SeedRange, - mapping: &[RangeMapping], - found_mapping: &mut CacheOfMappedRanges, -) -> Vec { - fn add_to_cache(range: SeedRange, cache: &mut CacheOfMappedRanges) { - let value = range.end(); - cache - .entry(range.start()) - .and_modify(|current_max| *current_max = (*current_max).max(value)) - .or_insert(value); - } - #[cfg(debug_assertions)] - { - let last = mapping.last().unwrap(); - assert!(last.source() == last.target(), "last: {:?}", last); - let to_test_for_sorting = mapping - .into_iter() - .map(|e| e.source()) - .collect::>(); - let is_sorted = crate::sequences::is_sorted(to_test_for_sorting.as_slice()); - assert!(is_sorted); - } - let mut new_branches = Vec::new(); - let mut currend_seed_range = seed_range; - for next in mapping { - match next.map_range(currend_seed_range) { - MappedSeedRange::OutsideFromLeft => (), - MappedSeedRange::Completely(done) => { - add_to_cache(done, found_mapping); - new_branches.push(done); - return new_branches; - } - MappedSeedRange::Partially { mapped, left } => { - add_to_cache(mapped, found_mapping); - new_branches.push(mapped); - currend_seed_range = left; - } - } - } - unreachable!(); -} - -fn combine_seeds_to_ranges(seeds: Vec) -> Vec { - seeds - .into_iter() - .into_chunks::<2>() - .map(|next_chunk| match next_chunk { - Chunck::Next([start, range]) => { - let end = start + range - 1; - SeedRange::new(start, end) - } - Chunck::Rest(_) => panic!("Uneven number of seeds"), - }) - .collect() -} - -#[cfg(test)] -mod testing { - use crate::solutions::day5::solve_task_2; - - #[test] - fn task2() { - let actual = solve_task_2(include_str!("day5_example_input.txt")); - assert_eq!("46", actual); - } -}