advent_of_code_2023_in_rust/crates/solutions/src/day5.rs
2024-08-30 06:03:08 +02:00

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),
],
);
}
}