From 4c093353cf33ed93d869c5b7d8579f715c94da14 Mon Sep 17 00:00:00 2001 From: BoolPurist Date: Sat, 17 Aug 2024 21:20:26 +0200 Subject: [PATCH] Restarted day 5 Implemented parsing for task 1 and 2 Implemented solution for task 1 --- 3 | 0 output.txt | 451 ++++++++++++++++++ src/iterations.rs | 70 +++ src/lib.rs | 2 + src/sequences.rs | 26 + src/solutions.rs | 2 +- src/solutions/day5.rs | 130 +++-- ...xample_input.txt => day_example_input.txt} | 0 src/solutions/day5/item_mapping.rs | 118 +++++ src/solutions/day5/mapping_layer.rs | 69 --- src/solutions/day5/parsing.rs | 56 +-- src/solutions/day5/range_mapping.rs | 156 ------ src/solutions/day5/seed_range.rs | 21 + ...yer__testing__should_fill_in_the_gaps.snap | 41 -- ...5__parsing__testing__parse_input_day5.snap | 186 -------- ..._day5__parsing__testing__should_parse.snap | 172 +++++++ src/solutions/day5/task2.rs | 129 +++++ 17 files changed, 1092 insertions(+), 537 deletions(-) create mode 100644 3 create mode 100644 output.txt create mode 100644 src/iterations.rs create mode 100644 src/sequences.rs rename src/solutions/day5/{day5_example_input.txt => day_example_input.txt} (100%) create mode 100644 src/solutions/day5/item_mapping.rs delete mode 100644 src/solutions/day5/mapping_layer.rs delete mode 100644 src/solutions/day5/range_mapping.rs create mode 100644 src/solutions/day5/seed_range.rs delete mode 100644 src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__mapping_layer__testing__should_fill_in_the_gaps.snap delete mode 100644 src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__parse_input_day5.snap create mode 100644 src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__should_parse.snap create mode 100644 src/solutions/day5/task2.rs diff --git a/3 b/3 new file mode 100644 index 0000000..e69de29 diff --git a/output.txt b/output.txt new file mode 100644 index 0000000..28d8fa0 --- /dev/null +++ b/output.txt @@ -0,0 +1,451 @@ + Compiling advent_of_code_2023 v0.1.0 (/home/nice_graphic/Code/AdventOfCode/advent_of_code_2023_in_rust) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74s + Running `target/debug/advent_of_code_2023 -d 5 -t 2 -r real_puzzel_input/day5_real.txt` +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 1742065688, + end: 1750102221, + }, + SeedRange { + start: 2567370277, + end: 2599723683, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 0, + end: 8416624, + }, + SeedRange { + start: 266989853, + end: 274550609, + }, + SeedRange { + start: 300866774, + end: 317512630, + }, + SeedRange { + start: 358218184, + end: 372723591, + }, + SeedRange { + start: 380891048, + end: 442413628, + }, + SeedRange { + start: 515244023, + end: 557308930, + }, + SeedRange { + start: 683066098, + end: 684855124, + }, + SeedRange { + start: 807721029, + end: 832371103, + }, + SeedRange { + start: 1194459751, + end: 1203805997, + }, + SeedRange { + start: 1468004524, + end: 1485692688, + }, + SeedRange { + start: 1543990204, + end: 1544270679, + }, + SeedRange { + start: 2018290756, + end: 2019801469, + }, + SeedRange { + start: 2019801470, + end: 2042706893, + }, + SeedRange { + start: 2115385660, + end: 2129003835, + }, + SeedRange { + start: 2129003836, + end: 2211102547, + }, + SeedRange { + start: 2392979056, + end: 2408003322, + }, + SeedRange { + start: 2607311497, + end: 2674349508, + }, + SeedRange { + start: 2746561268, + end: 2747062564, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 58753428, + end: 65411093, + }, + SeedRange { + start: 100997493, + end: 137201535, + }, + SeedRange { + start: 157986927, + end: 199863499, + }, + SeedRange { + start: 204313376, + end: 218545188, + }, + SeedRange { + start: 332621358, + end: 372723591, + }, + SeedRange { + start: 426571448, + end: 467405784, + }, + SeedRange { + start: 495575122, + end: 511974002, + }, + SeedRange { + start: 743910440, + end: 759938333, + }, + SeedRange { + start: 759938334, + end: 760087643, + }, + SeedRange { + start: 793045073, + end: 807721028, + }, + SeedRange { + start: 867655257, + end: 896123052, + }, + SeedRange { + start: 896123053, + end: 973947275, + }, + SeedRange { + start: 973947276, + end: 993981548, + }, + SeedRange { + start: 993981549, + end: 1002343672, + }, + SeedRange { + start: 1055927934, + end: 1095728907, + }, + SeedRange { + start: 1095728908, + end: 1119175102, + }, + SeedRange { + start: 1154854138, + end: 1180635906, + }, + SeedRange { + start: 1238423661, + end: 1248511724, + }, + SeedRange { + start: 1512990961, + end: 1523584140, + }, + SeedRange { + start: 1534040686, + end: 1546302601, + }, + SeedRange { + start: 1803258673, + end: 1809841397, + }, + SeedRange { + start: 1809841398, + end: 1841935458, + }, + SeedRange { + start: 1856531115, + end: 1913738155, + }, + SeedRange { + start: 2018290756, + end: 2028995369, + }, + SeedRange { + start: 2674349509, + end: 2681453545, + }, + SeedRange { + start: 2738910873, + end: 2744209881, + }, + SeedRange { + start: 2747062565, + end: 2783941215, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 475181169, + end: 490368072, + }, + SeedRange { + start: 836186785, + end: 843842518, + }, + SeedRange { + start: 2734840928, + end: 2738910872, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 244009039, + end: 254933582, + }, + SeedRange { + start: 305477485, + end: 329774690, + }, + SeedRange { + start: 442413629, + end: 458180769, + }, + SeedRange { + start: 1260160771, + end: 1272797031, + }, + SeedRange { + start: 1272797032, + end: 1286745099, + }, + SeedRange { + start: 1769136092, + end: 1817580770, + }, + SeedRange { + start: 1941952815, + end: 1959258660, + }, + SeedRange { + start: 2941559481, + end: 2963703887, + }, + SeedRange { + start: 3338671715, + end: 3392340230, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 332621358, + end: 351714897, + }, + SeedRange { + start: 372723592, + end: 390393835, + }, + SeedRange { + start: 426571448, + end: 467405784, + }, + SeedRange { + start: 1203805998, + end: 1248511724, + }, + SeedRange { + start: 1512990961, + end: 1523584140, + }, + SeedRange { + start: 1628354042, + end: 1634087664, + }, + SeedRange { + start: 2089724509, + end: 2193360445, + }, + SeedRange { + start: 2333003205, + end: 2348927163, + }, + SeedRange { + start: 2919622420, + end: 2941559480, + }, + SeedRange { + start: 3241659967, + end: 3257744758, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 137201536, + end: 157986926, + }, + SeedRange { + start: 218545189, + end: 220477638, + }, + SeedRange { + start: 600713965, + end: 665397099, + }, + SeedRange { + start: 832371104, + end: 836186784, + }, + SeedRange { + start: 843842519, + end: 867655256, + }, + SeedRange { + start: 1119175103, + end: 1138382174, + }, + SeedRange { + start: 1491903136, + end: 1499334672, + }, + SeedRange { + start: 1523584141, + end: 1543990203, + }, + SeedRange { + start: 2281428417, + end: 2289569154, + }, + SeedRange { + start: 2492935748, + end: 2514211768, + }, + SeedRange { + start: 2562030648, + end: 2567370276, + }, + SeedRange { + start: 2744209882, + end: 2758197105, + }, + SeedRange { + start: 2961556571, + end: 2970025309, + }, + SeedRange { + start: 2970025310, + end: 2975037528, + }, + SeedRange { + start: 3055485859, + end: 3060533366, + }, + SeedRange { + start: 3336845004, + end: 3337449016, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 372723592, + end: 390393835, + }, + SeedRange { + start: 1203805998, + end: 1230926691, + }, + SeedRange { + start: 1230926692, + end: 1234898502, + }, + SeedRange { + start: 1450460003, + end: 1468004523, + }, + SeedRange { + start: 1546302602, + end: 1600730425, + }, + SeedRange { + start: 1628354042, + end: 1634087664, + }, + SeedRange { + start: 1644303246, + end: 1684446508, + }, + SeedRange { + start: 1702371352, + end: 1750102221, + }, + SeedRange { + start: 1899676495, + end: 1926978899, + }, + SeedRange { + start: 1950759356, + end: 1994459585, + }, + SeedRange { + start: 2089724509, + end: 2193360445, + }, + SeedRange { + start: 2172619534, + end: 2188333609, + }, + SeedRange { + start: 2333003205, + end: 2348927163, + }, + SeedRange { + start: 2567370277, + end: 2607311496, + }, + SeedRange { + start: 2885502549, + end: 2941559480, + }, + SeedRange { + start: 3241659967, + end: 3257744758, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 1672236638, + end: 1707764533, + }, + SeedRange { + start: 2064249380, + end: 2073429343, + }, +] +[src/solutions/day5/task2.rs:42:5] ¤t_branches = [ + SeedRange { + start: 688038520, + end: 693497941, + }, + SeedRange { + start: 1485692689, + end: 1491903135, + }, + SeedRange { + start: 1750102222, + end: 1755861445, + }, + SeedRange { + start: 1775334238, + end: 1803258672, + }, +] diff --git a/src/iterations.rs b/src/iterations.rs new file mode 100644 index 0000000..acd508c --- /dev/null +++ b/src/iterations.rs @@ -0,0 +1,70 @@ +pub trait AdventIterator: Iterator { + fn into_chunks(self) -> impl Iterator> + where + Self: Sized, + { + in_chunks::(self) + } +} + +impl AdventIterator for I where I: Iterator {} + +#[derive(Debug, PartialEq, Eq)] +pub enum Chunck { + Next([T; N]), + Rest(Vec), +} + +pub fn in_chunks( + mut iterator: impl Iterator, +) -> impl Iterator> { + let mut buffer = Vec::new(); + let mut done = false; + std::iter::from_fn(move || { + if done { + return None; + } + for _ in 0..N { + match iterator.next() { + Some(to_push) => buffer.push(to_push), + None => { + done = true; + return if buffer.is_empty() { + None + } else { + Some(Chunck::Rest(std::mem::take(&mut buffer))) + }; + } + } + } + let array: [T; N] = std::mem::take(&mut buffer) + .try_into() + .unwrap_or_else(|_| panic!("Buffer must have the same size as the N ({})", N)); + Some(Chunck::Next(array)) + }) +} + +#[cfg(test)] +mod testing { + use crate::iterations::{in_chunks, Chunck}; + + #[test] + fn should_split_chunks() { + fn assert_case(input: Vec, expected: Vec>) { + let actual: Vec> = in_chunks::(input.into_iter()).collect(); + assert_eq!(expected, actual); + } + + assert_case::<2>(vec![2], vec![Chunck::Rest(vec![2])]); + assert_case::<2>(vec![], vec![]); + assert_case::<2>(vec![2, 2], vec![Chunck::Next([2, 2])]); + assert_case::<2>( + vec![1, 2, 3], + vec![Chunck::Next([1, 2]), Chunck::Rest(vec![3])], + ); + assert_case::<1>( + vec![1, 2, 3], + vec![Chunck::Next([1]), Chunck::Next([2]), Chunck::Next([3])], + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index b531276..fa2c6aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ pub mod cli; pub mod constants; +pub mod iterations; pub mod parsing_utils; +pub mod sequences; pub mod solutions; diff --git a/src/sequences.rs b/src/sequences.rs new file mode 100644 index 0000000..681d449 --- /dev/null +++ b/src/sequences.rs @@ -0,0 +1,26 @@ +pub fn is_sorted(shoube_be_sorted: &[T]) -> bool +where + T: PartialOrd + Ord + Clone + PartialEq + Eq, +{ + let mut sorted: Vec = shoube_be_sorted.into_iter().cloned().collect(); + sorted.sort(); + sorted.as_slice().eq(shoube_be_sorted) +} + +#[cfg(test)] +mod testing { + + #[test] + fn should_detect_if_is_sorted() { + fn assert_case(input: &[u32], expected: bool) { + let actual = super::is_sorted(input); + assert_eq!(expected, actual, "Input {:#?}", input); + } + + assert_case(&[1, 2], true); + assert_case(&[2, 1, 2], false); + assert_case(&[2, 1], false); + assert_case(&[1], true); + assert_case(&[], true); + } +} diff --git a/src/solutions.rs b/src/solutions.rs index 120b419..b6dc2fd 100644 --- a/src/solutions.rs +++ b/src/solutions.rs @@ -6,7 +6,7 @@ pub fn create_solutions() -> Vec String>> { vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet], - vec![day5::solve_task_1, not_implemented_yet], + vec![day5::solve_task_1, day5::solve_task_2], vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet], diff --git a/src/solutions/day5.rs b/src/solutions/day5.rs index 1e53498..6e63db7 100644 --- a/src/solutions/day5.rs +++ b/src/solutions/day5.rs @@ -1,68 +1,94 @@ -use mapping_layer::MappingLayer; +use item_mapping::ItemMapping; +use seed_range::SeedRange; -type UnsignedNumber = u64; -mod mapping_layer; +mod item_mapping; mod parsing; -mod range_mapping; +mod seed_range; +type UnsignedNumber = u128; + pub fn solve_task_1(input: &str) -> String { - let (seeds, layers) = parsing::parse(input); - let location = get_lowest_locations_from(&seeds, &layers); - location.to_string() + let (seeds, mapping_layers) = parsing::parse(input); + let min = min_location_from_given_single_seeds(&seeds, &mapping_layers); + min.to_string() } -fn get_lowest_locations_from(seeds: &[UnsignedNumber], layers: &[MappingLayer]) -> UnsignedNumber { - seeds - .into_iter() - .map(|next_seed| location_of_one_point(*next_seed, layers)) - .min() - .unwrap() -} - -fn location_of_one_point(seed: UnsignedNumber, layers: &[MappingLayer]) -> UnsignedNumber { - let mut current_position = seed; - for next_layer in layers { - let ranges = next_layer.ranges(); - let range_to_use = match ranges - .binary_search_by_key(¤t_position, |extract_source| extract_source.source()) - { - Ok(exact) => ranges - .get(exact) - .expect("Exact index can not be out of bounds"), - Err(not_exact) => { - let not_exact = not_exact.clamp(0, ranges.len() - 1); - let source = ranges.get(not_exact).unwrap(); - let source_pos = source.source(); - if current_position > source_pos { - source - } else { - ranges.get(not_exact.saturating_sub(1)).unwrap() +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 { + match next_mapping.map_point(current_seed) { + Some(found_mapping) => { + current_seed = found_mapping; + break; + } + None => (), } } - }; - current_position = range_to_use.map_position(current_position); + } + min = current_seed.min(min); } - current_position + 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) + }; + "".to_string() +} + +fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec { + assert!(single_seeds.len() % 2 == 0); + single_seeds + .chunks(2) + .into_iter() + .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 super::*; + use crate::solutions::day5::{ + combine_single_to_ranges_for_seeds, parsing, seed_range::SeedRange, solve_task_2, + }; + + use super::solve_task_1; + const EXAMPLE_INPUT: &str = include_str!("day5/day_example_input.txt"); - // Seed 79, soil 81, fertilizer 81, water 81, light 74, temperature 78, humidity 78, location 82. - // Seed 14, soil 14, fertilizer 53, water 49, light 42, temperature 42, humidity 43, location 43. - // Seed 55, soil 57, fertilizer 57, water 53, light 46, temperature 82, humidity 82, location 86. - // Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34, humidity 35, location 35. #[test] - fn name() { - fn assert_case(seeds: UnsignedNumber, layer: &[MappingLayer], expected: UnsignedNumber) { - let actual = location_of_one_point(seeds, &layer); - assert_eq!(expected, actual, "Given seeds: {}", seeds); - } - let input = parsing::parse(include_str!("day5/day5_example_input.txt")); - let layers = &input.1; - assert_case(79, layers, 82); - assert_case(14, layers, 43); - assert_case(55, layers, 86); - assert_case(13, layers, 35); + 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) } } diff --git a/src/solutions/day5/day5_example_input.txt b/src/solutions/day5/day_example_input.txt similarity index 100% rename from src/solutions/day5/day5_example_input.txt rename to src/solutions/day5/day_example_input.txt diff --git a/src/solutions/day5/item_mapping.rs b/src/solutions/day5/item_mapping.rs new file mode 100644 index 0000000..1067f77 --- /dev/null +++ b/src/solutions/day5/item_mapping.rs @@ -0,0 +1,118 @@ +use std::str::FromStr; + +use thiserror::Error; + +use super::{seed_range::SeedRange, UnsignedNumber}; + +#[derive(Debug, PartialEq, Eq)] +pub struct ItemMapping { + source_range: SeedRange, + target: UnsignedNumber, + range: UnsignedNumber, +} + +impl ItemMapping { + 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; + Some(mapped) + } else { + None + } + } +} + +#[derive(Debug, Error, PartialEq, Eq)] +#[error("Item mapping must be exactly 3 numbers")] +pub struct InvalidStrItemMapping; + +impl FromStr for ItemMapping { + type Err = InvalidStrItemMapping; + + fn from_str(s: &str) -> Result { + let mut splitted = s.split_whitespace().take(3).map(|to_parse| { + to_parse + .parse::() + .map_err(|_| InvalidStrItemMapping) + }); + match (splitted.next(), splitted.next(), splitted.next()) { + (Some(Ok(target)), Some(Ok(source)), Some(Ok(range))) => { + let inclusive_range = range.saturating_sub(1); + let end = source + inclusive_range; + let source_range = SeedRange::new(source, end); + let parsed_self = Self { + source_range, + target, + range, + }; + Ok(parsed_self) + } + (_, _, _) => Err(InvalidStrItemMapping), + } + } +} + +#[cfg(test)] +mod testing { + use crate::solutions::day5::{ + item_mapping::{InvalidStrItemMapping, ItemMapping}, + seed_range::SeedRange, + UnsignedNumber, + }; + + #[test] + fn from_str_item_mapping() { + fn assert_case(input: &str, expected: Result) { + let actual = input.parse(); + assert_eq!(expected, actual, "Input: {}", input); + } + assert_case( + "20 10 22", + Ok(ItemMapping { + source_range: SeedRange::new(10, 31), + target: 20, + range: 22, + }), + ); + assert_case( + "50 98 2", + Ok(ItemMapping { + source_range: SeedRange::new(98, 99), + target: 50, + range: 2, + }), + ); + assert_case( + "52 50 48", + Ok(ItemMapping { + source_range: SeedRange::new(50, 97), + target: 52, + range: 48, + }), + ); + assert_case("a 10 22", Err(InvalidStrItemMapping)); + assert_case("10 10", Err(InvalidStrItemMapping)); + } + #[test] + fn should_map_point() { + fn assert_case(point: UnsignedNumber, input: &str, expected: Option) { + let mapping: ItemMapping = input.parse().unwrap(); + let actual = mapping.map_point(point); + + assert_eq!( + expected, actual, + "Point: {:?}\nMaping: {:?}", + point, mapping + ); + } + + assert_case(2, "4 2 2", Some(4)); + assert_case(0, "4 2 2", None); + assert_case(4, "4 2 2", None); + assert_case(79, "52 50 48", Some(81)); + } +} diff --git a/src/solutions/day5/mapping_layer.rs b/src/solutions/day5/mapping_layer.rs deleted file mode 100644 index 1e40c55..0000000 --- a/src/solutions/day5/mapping_layer.rs +++ /dev/null @@ -1,69 +0,0 @@ -use derive_more::derive; - -use super::range_mapping::RangeMapping; - -#[derive(Debug)] -pub struct MappingLayer { - label: String, - ranges: Vec, -} - -impl MappingLayer { - pub fn new(label: impl Into, mut ranges: Vec) -> Self { - ranges.sort_by_key(|element| element.source()); - let ranges = Self::fill_in_the_gaps(ranges); - Self { - label: label.into(), - ranges, - } - } - - /// # Expected - /// It is assumed that ranges is sorted by the field `source` - pub fn fill_in_the_gaps(ranges: Vec) -> Vec { - let mut current_source_start = 0; - let mut without_gaps: Vec = Vec::new(); - for next_range in ranges { - let next_source = next_range.source(); - - assert!(current_source_start <= next_source); - if current_source_start == next_source { - current_source_start = next_range.inclusive_end_source().saturating_add(1); - without_gaps.push(next_range); - } else { - let new_range = next_source; - without_gaps.push(RangeMapping::just_with_source( - current_source_start, - new_range, - )); - current_source_start = next_range.inclusive_end_source() + 1; - without_gaps.push(next_range); - } - } - - without_gaps.push(RangeMapping::source_to_max(current_source_start)); - - without_gaps - } - - pub fn ranges(&self) -> &[RangeMapping] { - &self.ranges - } -} - -#[cfg(test)] -mod testing { - use crate::solutions::day5::{mapping_layer::MappingLayer, range_mapping::RangeMapping}; - - #[test] - fn should_fill_in_the_gaps() { - let input = vec![ - RangeMapping::new(45, 81, 19), - RangeMapping::new(64, 68, 13), - RangeMapping::new(77, 45, 23), - RangeMapping::new(110, 55, 20), - ]; - let actual = MappingLayer::fill_in_the_gaps(input); - insta::assert_debug_snapshot!(actual); - } -} diff --git a/src/solutions/day5/parsing.rs b/src/solutions/day5/parsing.rs index 730772f..ad11b19 100644 --- a/src/solutions/day5/parsing.rs +++ b/src/solutions/day5/parsing.rs @@ -1,46 +1,38 @@ -use crate::{parsing_utils, solutions::day5::range_mapping::RangeMapping}; +use crate::parsing_utils; -use super::{mapping_layer::MappingLayer, UnsignedNumber}; +use super::{item_mapping::ItemMapping, UnsignedNumber}; -pub fn parse(input: &str) -> (Vec, Vec) { - fn parse_one_layer(lines: Vec<&str>) -> MappingLayer { - const ALREADY_GOT_MAPPING_NAME: usize = 1; - let mapping_name = *lines.first().unwrap(); - let mut mappings = Vec::new(); - - for next_line in lines.into_iter().skip(ALREADY_GOT_MAPPING_NAME) { - let next_mapping: RangeMapping = next_line.parse().unwrap(); - mappings.push(next_mapping); - } - - MappingLayer::new(mapping_name.to_string(), mappings) - } +pub fn parse(input: &str) -> (Vec, Vec>) { let mut blocks = parsing_utils::blocks_of_lines_seperated_by_empty_lines(input); - let first_block = blocks.next().unwrap(); + let seeds = { + let first_block = blocks.next().unwrap(); + let first_line = first_block.first().unwrap(); + first_line + .strip_prefix("seeds:") + .unwrap() + .split_whitespace() + .map(|not_parsed| not_parsed.parse::().unwrap()) + .collect() + }; - assert!(first_block.len() == 1); - let seeds = first_block - .first() - .unwrap() - .trim_start_matches("seeds: ") - .split(" ") - .map(|to_number| to_number.parse().unwrap()) + let mappings: Vec> = blocks + .map(|lines| { + lines + .into_iter() + .skip(1) + .map(|to_parse| to_parse.parse::().unwrap()) + .collect() + }) .collect(); - let mappings = blocks.into_iter().map(parse_one_layer).collect(); - (seeds, mappings) } #[cfg(test)] mod testing { - use crate::solutions::day5::UnsignedNumber; - #[test] - fn parse_input_day5() { - let input = include_str!("day5_example_input.txt"); + fn should_parse() { + let input = include_str!("day_example_input.txt"); let actual = super::parse(input); - let expected_seeds: Vec = vec![79, 14, 55, 13]; - assert_eq!(expected_seeds, actual.0); - insta::assert_debug_snapshot!(actual.1); + insta::assert_debug_snapshot!(actual); } } diff --git a/src/solutions/day5/range_mapping.rs b/src/solutions/day5/range_mapping.rs deleted file mode 100644 index cefa836..0000000 --- a/src/solutions/day5/range_mapping.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::{str::FromStr, u32}; - -use thiserror::Error; - -use super::UnsignedNumber; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct RangeMapping { - source: UnsignedNumber, - target: UnsignedNumber, - range: UnsignedNumber, -} - -#[derive(Debug, Error)] -pub enum ParseErrorRangeMapping { - #[error("Needs to have at least 3 numbers")] - LessThan3Numbers, - #[error("Every item must be a valid unsigned number")] - InvalidFormatForNumbers, -} - -impl FromStr for RangeMapping { - type Err = ParseErrorRangeMapping; - - fn from_str(s: &str) -> Result { - let mut numbers_as_3 = s.split(" ").take(3).map(|unparsed| { - unparsed - .parse::() - .map_err(|_| ParseErrorRangeMapping::InvalidFormatForNumbers) - }); - let (target, source, range) = ( - numbers_as_3 - .next() - .ok_or(ParseErrorRangeMapping::LessThan3Numbers)??, - numbers_as_3 - .next() - .ok_or(ParseErrorRangeMapping::LessThan3Numbers)??, - numbers_as_3 - .next() - .ok_or(ParseErrorRangeMapping::LessThan3Numbers)??, - ); - - Ok(Self { - target, - source, - range, - }) - } -} - -impl RangeMapping { - pub fn new(source: UnsignedNumber, target: UnsignedNumber, range: UnsignedNumber) -> Self { - Self { - source, - target, - range, - } - } - - pub fn just_with_source(source: UnsignedNumber, range: UnsignedNumber) -> Self { - Self { - source, - target: source, - range, - } - } - - pub fn source_to_max(source: UnsignedNumber) -> Self { - const ENSURES_SOURCE_END_IS_MAX: UnsignedNumber = 1; - let range = if source == 0 { - UnsignedNumber::MAX - } else { - UnsignedNumber::MAX - (source - ENSURES_SOURCE_END_IS_MAX) - }; - - Self::just_with_source(source, range) - } - - pub fn source(&self) -> UnsignedNumber { - self.source - } - - pub fn inclusive_end_source(&self) -> UnsignedNumber { - if self.range() == UnsignedNumber::MAX { - UnsignedNumber::MAX - } else { - self.source() + (self.range() - 1) - } - } - - pub fn map_position(&self, position: UnsignedNumber) -> UnsignedNumber { - let inclusive_end_source = self.inclusive_end_source(); - if self.source() > position || position > inclusive_end_source { - panic!( - "Given position ({}) is outside of this range between ({}) and ({})", - position, - self.source(), - inclusive_end_source - ); - } - let difference = position - self.source(); - self.target() + difference - } - - pub fn target(&self) -> UnsignedNumber { - self.target - } - - pub fn range(&self) -> UnsignedNumber { - self.range - } -} - -#[cfg(test)] -mod testing { - use std::u32; - - use super::*; - - #[test] - fn parse_from_line() { - const INPUT: &str = "50 98 2"; - let expected = RangeMapping { - source: 98, - target: 50, - range: 2, - }; - let actual: RangeMapping = INPUT.parse().unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn calc_inclusive_end_source() { - fn assert_case(given: RangeMapping, expected: UnsignedNumber) { - let actual = given.inclusive_end_source(); - assert_eq!(expected, actual, "Given: {:#?}", given); - } - assert_case(RangeMapping::new(98, 50, 2), 99); - assert_case(RangeMapping::new(0, 50, 1), 0); - assert_case(RangeMapping::new(10, 50, 5), 14); - } - - #[test] - fn create_from_source_to_max_integer_max_correctly() { - fn assert_case(input: RangeMapping) { - const EXPECTED: UnsignedNumber = UnsignedNumber::MAX; - let actual = input.inclusive_end_source(); - assert_eq!(EXPECTED, actual, "Given: {:#?}", input); - } - assert_case(RangeMapping::source_to_max(UnsignedNumber::MAX)); - assert_case(RangeMapping::source_to_max(UnsignedNumber::MAX - 1)); - assert_case(RangeMapping::source_to_max(100)); - assert_case(RangeMapping::source_to_max(2)); - assert_case(RangeMapping::source_to_max(0)); - } -} diff --git a/src/solutions/day5/seed_range.rs b/src/solutions/day5/seed_range.rs new file mode 100644 index 0000000..b62fbc7 --- /dev/null +++ b/src/solutions/day5/seed_range.rs @@ -0,0 +1,21 @@ +use super::UnsignedNumber; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct SeedRange { + start: UnsignedNumber, + end: UnsignedNumber, +} + +impl SeedRange { + pub fn new(start: UnsignedNumber, end: UnsignedNumber) -> Self { + Self { start, end } + } + + pub fn start(&self) -> UnsignedNumber { + self.start + } + + pub fn end(&self) -> UnsignedNumber { + self.end + } +} diff --git a/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__mapping_layer__testing__should_fill_in_the_gaps.snap b/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__mapping_layer__testing__should_fill_in_the_gaps.snap deleted file mode 100644 index cb2a2cf..0000000 --- a/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__mapping_layer__testing__should_fill_in_the_gaps.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: src/solutions/day5/mapping_layer.rs -expression: actual ---- -[ - RangeMapping { - source: 0, - target: 0, - range: 45, - }, - RangeMapping { - source: 45, - target: 81, - range: 19, - }, - RangeMapping { - source: 64, - target: 68, - range: 13, - }, - RangeMapping { - source: 77, - target: 45, - range: 23, - }, - RangeMapping { - source: 100, - target: 100, - range: 110, - }, - RangeMapping { - source: 110, - target: 55, - range: 20, - }, - RangeMapping { - source: 130, - target: 130, - range: 18446744073709551486, - }, -] diff --git a/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__parse_input_day5.snap b/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__parse_input_day5.snap deleted file mode 100644 index 01c324f..0000000 --- a/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__parse_input_day5.snap +++ /dev/null @@ -1,186 +0,0 @@ ---- -source: src/solutions/day5/parsing.rs -expression: actual.1 ---- -[ - MappingLayer { - label: "seed-to-soil map:", - ranges: [ - RangeMapping { - source: 0, - target: 0, - range: 50, - }, - RangeMapping { - source: 50, - target: 52, - range: 48, - }, - RangeMapping { - source: 98, - target: 50, - range: 2, - }, - RangeMapping { - source: 100, - target: 100, - range: 18446744073709551516, - }, - ], - }, - MappingLayer { - label: "soil-to-fertilizer map:", - ranges: [ - RangeMapping { - source: 0, - target: 39, - range: 15, - }, - RangeMapping { - source: 15, - target: 0, - range: 37, - }, - RangeMapping { - source: 52, - target: 37, - range: 2, - }, - RangeMapping { - source: 54, - target: 54, - range: 18446744073709551562, - }, - ], - }, - MappingLayer { - label: "fertilizer-to-water map:", - ranges: [ - RangeMapping { - source: 0, - target: 42, - range: 7, - }, - RangeMapping { - source: 7, - target: 57, - range: 4, - }, - RangeMapping { - source: 11, - target: 0, - range: 42, - }, - RangeMapping { - source: 53, - target: 49, - range: 8, - }, - RangeMapping { - source: 61, - target: 61, - range: 18446744073709551555, - }, - ], - }, - MappingLayer { - label: "water-to-light map:", - ranges: [ - RangeMapping { - source: 0, - target: 0, - range: 18, - }, - RangeMapping { - source: 18, - target: 88, - range: 7, - }, - RangeMapping { - source: 25, - target: 18, - range: 70, - }, - RangeMapping { - source: 95, - target: 95, - range: 18446744073709551521, - }, - ], - }, - MappingLayer { - label: "light-to-temperature map:", - ranges: [ - RangeMapping { - source: 0, - target: 0, - range: 45, - }, - RangeMapping { - source: 45, - target: 81, - range: 19, - }, - RangeMapping { - source: 64, - target: 68, - range: 13, - }, - RangeMapping { - source: 77, - target: 45, - range: 23, - }, - RangeMapping { - source: 100, - target: 100, - range: 18446744073709551516, - }, - ], - }, - MappingLayer { - label: "temperature-to-humidity map:", - ranges: [ - RangeMapping { - source: 0, - target: 1, - range: 69, - }, - RangeMapping { - source: 69, - target: 0, - range: 1, - }, - RangeMapping { - source: 70, - target: 70, - range: 18446744073709551546, - }, - ], - }, - MappingLayer { - label: "humidity-to-location map:", - ranges: [ - RangeMapping { - source: 0, - target: 0, - range: 56, - }, - RangeMapping { - source: 56, - target: 60, - range: 37, - }, - RangeMapping { - source: 93, - target: 56, - range: 4, - }, - RangeMapping { - source: 97, - target: 97, - range: 18446744073709551519, - }, - ], - }, -] diff --git a/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__should_parse.snap b/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__should_parse.snap new file mode 100644 index 0000000..979f1c4 --- /dev/null +++ b/src/solutions/day5/snapshots/advent_of_code_2023__solutions__day5__parsing__testing__should_parse.snap @@ -0,0 +1,172 @@ +--- +source: src/solutions/day5/parsing.rs +expression: actual +--- +( + [ + 79, + 14, + 55, + 13, + ], + [ + [ + ItemMapping { + source_range: SeedRange { + start: 98, + end: 99, + }, + target: 50, + range: 2, + }, + ItemMapping { + source_range: SeedRange { + start: 50, + end: 97, + }, + target: 52, + range: 48, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 15, + end: 51, + }, + target: 0, + range: 37, + }, + ItemMapping { + source_range: SeedRange { + start: 52, + end: 53, + }, + target: 37, + range: 2, + }, + ItemMapping { + source_range: SeedRange { + start: 0, + end: 14, + }, + target: 39, + range: 15, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 53, + end: 60, + }, + target: 49, + range: 8, + }, + ItemMapping { + source_range: SeedRange { + start: 11, + end: 52, + }, + target: 0, + range: 42, + }, + ItemMapping { + source_range: SeedRange { + start: 0, + end: 6, + }, + target: 42, + range: 7, + }, + ItemMapping { + source_range: SeedRange { + start: 7, + end: 10, + }, + target: 57, + range: 4, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 18, + end: 24, + }, + target: 88, + range: 7, + }, + ItemMapping { + source_range: SeedRange { + start: 25, + end: 94, + }, + target: 18, + range: 70, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 77, + end: 99, + }, + target: 45, + range: 23, + }, + ItemMapping { + source_range: SeedRange { + start: 45, + end: 63, + }, + target: 81, + range: 19, + }, + ItemMapping { + source_range: SeedRange { + start: 64, + end: 76, + }, + target: 68, + range: 13, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 69, + end: 69, + }, + target: 0, + range: 1, + }, + ItemMapping { + source_range: SeedRange { + start: 0, + end: 68, + }, + target: 1, + range: 69, + }, + ], + [ + ItemMapping { + source_range: SeedRange { + start: 56, + end: 92, + }, + target: 60, + range: 37, + }, + ItemMapping { + source_range: SeedRange { + start: 93, + end: 96, + }, + target: 56, + range: 4, + }, + ], + ], +) diff --git a/src/solutions/day5/task2.rs b/src/solutions/day5/task2.rs new file mode 100644 index 0000000..4adc8d4 --- /dev/null +++ b/src/solutions/day5/task2.rs @@ -0,0 +1,129 @@ +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); + } +}