Solved second part of day 5

This commit is contained in:
BoolPurist 2024-08-18 02:15:01 +02:00
parent 4c093353cf
commit 9a10cf7c47
5 changed files with 307 additions and 140 deletions

0
3
View file

View file

@ -1,4 +1,4 @@
use item_mapping::ItemMapping; use item_mapping::{ItemMapping, MappedRange};
use seed_range::SeedRange; use seed_range::SeedRange;
mod item_mapping; 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); let seed_ranges = combine_single_to_ranges_for_seeds(&single_seeds);
(seed_ranges, mappings) (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<ItemMapping>],
) -> UnsignedNumber {
let mut for_next_round: Vec<SeedRange> = 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<SeedRange> {
let (mut to_map, mut mapped_ranges): (Vec<SeedRange>, Vec<SeedRange>) =
(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<SeedRange> { fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec<SeedRange> {
@ -63,8 +134,11 @@ fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec<Se
#[cfg(test)] #[cfg(test)]
mod testing { mod testing {
use std::str::FromStr;
use crate::solutions::day5::{ use crate::solutions::day5::{
combine_single_to_ranges_for_seeds, parsing, seed_range::SeedRange, solve_task_2, 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; use super::solve_task_1;
@ -91,4 +165,70 @@ mod testing {
let expected = "46"; let expected = "46";
assert_eq!(expected, actual) assert_eq!(expected, actual)
} }
fn create_first_round_of_example() -> Vec<ItemMapping> {
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<SeedRange>,
) {
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),
],
);
}
} }

View file

@ -1,10 +1,10 @@
use std::str::FromStr; use std::{str::FromStr};
use thiserror::Error; use thiserror::Error;
use super::{seed_range::SeedRange, UnsignedNumber}; use super::{seed_range::SeedRange, UnsignedNumber};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct ItemMapping { pub struct ItemMapping {
source_range: SeedRange, source_range: SeedRange,
target: UnsignedNumber, target: UnsignedNumber,
@ -12,13 +12,84 @@ pub struct ItemMapping {
} }
impl 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<UnsignedNumber> { pub fn map_point(&self, to_map: UnsignedNumber) -> Option<UnsignedNumber> {
let source_range = self.source_range; let source_range = self.source_range;
let start = source_range.start(); let start = source_range.start();
let end = source_range.end(); let end = source_range.end(); if start <= to_map && end >= to_map {
if start <= to_map && end >= to_map { let mapped = self.offset_mapping(to_map);
let difference = to_map - start;
let mapped = self.target + difference;
Some(mapped) Some(mapped)
} else { } else {
None 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)] #[derive(Debug, Error, PartialEq, Eq)]
#[error("Item mapping must be exactly 3 numbers")] #[error("Item mapping must be exactly 3 numbers")]
pub struct InvalidStrItemMapping; pub struct InvalidStrItemMapping;
@ -59,7 +144,7 @@ impl FromStr for ItemMapping {
#[cfg(test)] #[cfg(test)]
mod testing { mod testing {
use crate::solutions::day5::{ use crate::solutions::day5::{
item_mapping::{InvalidStrItemMapping, ItemMapping}, item_mapping::{InvalidStrItemMapping, ItemMapping, MappedRange},
seed_range::SeedRange, seed_range::SeedRange,
UnsignedNumber, UnsignedNumber,
}; };
@ -115,4 +200,74 @@ mod testing {
assert_case(4, "4 2 2", None); assert_case(4, "4 2 2", None);
assert_case(79, "52 50 48", Some(81)); 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),
},
);
}
} }

View file

@ -1,6 +1,6 @@
use super::UnsignedNumber; use super::UnsignedNumber;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub struct SeedRange { pub struct SeedRange {
start: UnsignedNumber, start: UnsignedNumber,
end: UnsignedNumber, end: UnsignedNumber,
@ -8,6 +8,7 @@ pub struct SeedRange {
impl SeedRange { impl SeedRange {
pub fn new(start: UnsignedNumber, end: UnsignedNumber) -> Self { pub fn new(start: UnsignedNumber, end: UnsignedNumber) -> Self {
assert!(end >= start);
Self { start, end } Self { start, end }
} }

View file

@ -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<UnsignedNumber> = 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<UnsignedNumber, UnsignedNumber>;
fn map_sequence(
seed_range: SeedRange,
mapping: &[RangeMapping],
found_mapping: &mut CacheOfMappedRanges,
) -> Vec<SeedRange> {
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::<Vec<UnsignedNumber>>();
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<UnsignedNumber>) -> Vec<SeedRange> {
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);
}
}