Solved second part of day 5
This commit is contained in:
parent
4c093353cf
commit
9a10cf7c47
5 changed files with 307 additions and 140 deletions
0
3
0
3
|
@ -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<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> {
|
||||
|
@ -63,8 +134,11 @@ fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec<Se
|
|||
|
||||
#[cfg(test)]
|
||||
mod testing {
|
||||
use std::str::FromStr;
|
||||
|
||||
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;
|
||||
|
@ -91,4 +165,70 @@ mod testing {
|
|||
let expected = "46";
|
||||
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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<UnsignedNumber> {
|
||||
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),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue