230 lines
6.9 KiB
Rust
230 lines
6.9 KiB
Rust
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<ItemMapping>],
|
|
) -> 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<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> {
|
|
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<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),
|
|
],
|
|
);
|
|
}
|
|
}
|