Compare commits

..

No commits in common. "9a10cf7c47c39bec3e67a04da091805297a1fa30" and "cb7e8465a2342048f0d10c73834dfa3bb6a7e03f" have entirely different histories.

17 changed files with 341 additions and 1264 deletions

2
.gitignore vendored
View file

@ -1,3 +1 @@
/target
/real_puzzel_input/*
!/real_puzzel_input/.gitkeep

60
\ Normal file
View file

@ -0,0 +1,60 @@
use std::{fs, io, path::Path, process::ExitCode};
use advent_of_code_2023::{cli::AppCliArguments, solutions};
use clap::Parser;
use thiserror::Error;
fn main() -> ExitCode {
let args = AppCliArguments::parse();
let solution = solve_given(&args);
match solution {
Ok(found_solution) => {
println!("{}", found_solution);
ExitCode::SUCCESS
}
Err(error) => {
eprintln!("{}", error);
ExitCode::FAILURE
}
}
}
fn solve_given(args: &AppCliArguments) -> Result<String, CouldNotSolveError> {
let all_solutions = solutions::create_solutions();
let found_task = {
let day: u32 = args.day().into();
let task: u32 = args.task().into();
let found_day = all_solutions
.get(day.saturating_sub(1) as usize)
.ok_or_else(|| CouldNotSolveError::DayNotFound(day))?;
found_day
.get(task.saturating_sub(1) as usize)
.ok_or_else(|| CouldNotSolveError::TaskNotFound { day, task })
}?;
let solved = (found_task)(args.input());
Ok(solved)
}
fn try_read_from_file_if_demanded(args: &AppCliArguments) -> io::Result<String> {
let content = if args.read_as_file() {
let path = Path::new(args.input());
let input_as_file = fs::read_to_string(path)?;
input_as_file
} else {
args.input().to_string()
};
Ok(content)
}
#[derive(Debug, Clone, Copy, Error)]
enum CouldNotSolveError {
#[error("There is no solution for the day {0}")]
DayNotFound(u32),
#[error("There is not solution for task {task} under the day {day}")]
TaskNotFound { day: u32, task: u32 },
#[error("Could not read puzzel input from the given file\n {0}")]
CouldNotReadFromFile(#[from] io::Error),
}

View file

@ -1,451 +0,0 @@
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] &current_branches = [
SeedRange {
start: 1742065688,
end: 1750102221,
},
SeedRange {
start: 2567370277,
end: 2599723683,
},
]
[src/solutions/day5/task2.rs:42:5] &current_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] &current_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] &current_branches = [
SeedRange {
start: 475181169,
end: 490368072,
},
SeedRange {
start: 836186785,
end: 843842518,
},
SeedRange {
start: 2734840928,
end: 2738910872,
},
]
[src/solutions/day5/task2.rs:42:5] &current_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] &current_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] &current_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] &current_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] &current_branches = [
SeedRange {
start: 1672236638,
end: 1707764533,
},
SeedRange {
start: 2064249380,
end: 2073429343,
},
]
[src/solutions/day5/task2.rs:42:5] &current_branches = [
SeedRange {
start: 688038520,
end: 693497941,
},
SeedRange {
start: 1485692689,
end: 1491903135,
},
SeedRange {
start: 1750102222,
end: 1755861445,
},
SeedRange {
start: 1775334238,
end: 1803258672,
},
]

View file

@ -1,70 +0,0 @@
pub trait AdventIterator: Iterator {
fn into_chunks<const N: usize>(self) -> impl Iterator<Item = Chunck<N, Self::Item>>
where
Self: Sized,
{
in_chunks::<N, Self::Item>(self)
}
}
impl<I> AdventIterator for I where I: Iterator {}
#[derive(Debug, PartialEq, Eq)]
pub enum Chunck<const N: usize, T> {
Next([T; N]),
Rest(Vec<T>),
}
pub fn in_chunks<const N: usize, T>(
mut iterator: impl Iterator<Item = T>,
) -> impl Iterator<Item = Chunck<N, T>> {
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<const N: usize>(input: Vec<u32>, expected: Vec<Chunck<N, u32>>) {
let actual: Vec<Chunck<N, u32>> = in_chunks::<N, u32>(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])],
);
}
}

View file

@ -1,6 +1,4 @@
pub mod cli;
pub mod constants;
pub mod iterations;
pub mod parsing_utils;
pub mod sequences;
pub mod solutions;

View file

@ -1,26 +0,0 @@
pub fn is_sorted<T>(shoube_be_sorted: &[T]) -> bool
where
T: PartialOrd + Ord + Clone + PartialEq + Eq,
{
let mut sorted: Vec<T> = 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);
}
}

View file

@ -2,11 +2,11 @@ pub mod day5;
pub fn create_solutions() -> Vec<Vec<fn(&str) -> String>> {
vec![
vec![not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet],
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, day5::solve_task_2],
vec![day5::solve_task_1, not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet],

View file

@ -1,234 +1,36 @@
use item_mapping::{ItemMapping, MappedRange};
use seed_range::SeedRange;
use mapping_layer::MappingLayer;
mod item_mapping;
mod mapping_layer;
mod parsing;
mod seed_range;
type UnsignedNumber = u128;
mod range_mapping;
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()
let _parsed = parsing::parse(input);
todo!()
}
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 {
match next_mapping.map_point(current_seed) {
Some(found_mapping) => {
current_seed = found_mapping;
break;
}
None => (),
}
}
}
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)
.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()
fn location_of_one_point(seeds: u32, layers: &[MappingLayer]) -> u32 {
todo!()
}
#[cfg(test)]
mod testing {
use std::str::FromStr;
use crate::solutions::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");
use super::*;
// 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 should_solve_task_1() {
let actual = solve_task_1(EXAMPLE_INPUT);
let expected = "35";
assert_eq!(expected, actual)
fn name() {
fn assert_case(seeds: u32, layer: &[MappingLayer], expected: u32) {
let actual = location_of_one_point(seeds, layer);
assert_eq!(expected, actual, "Given seeds: {}", seeds);
}
#[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),
],
);
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);
}
}

View file

@ -1,273 +0,0 @@
use std::{str::FromStr};
use thiserror::Error;
use super::{seed_range::SeedRange, UnsignedNumber};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ItemMapping {
source_range: SeedRange,
target: UnsignedNumber,
range: UnsignedNumber,
}
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 mapped = self.offset_mapping(to_map);
Some(mapped)
} else {
None
}
}
}
#[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;
impl FromStr for ItemMapping {
type Err = InvalidStrItemMapping;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut splitted = s.split_whitespace().take(3).map(|to_parse| {
to_parse
.parse::<UnsignedNumber>()
.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, MappedRange},
seed_range::SeedRange,
UnsignedNumber,
};
#[test]
fn from_str_item_mapping() {
fn assert_case(input: &str, expected: Result<ItemMapping, InvalidStrItemMapping>) {
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<UnsignedNumber>) {
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));
}
#[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

@ -0,0 +1,19 @@
use derive_more::derive;
use super::range_mapping::RangeMapping;
#[derive(Debug)]
pub struct MappingLayer {
label: String,
ranges: Vec<RangeMapping>,
}
impl MappingLayer {
pub fn new(label: impl Into<String>, mut ranges: Vec<RangeMapping>) -> Self {
ranges.sort_by_key(|element| element.source());
Self {
label: label.into(),
ranges,
}
}
}

View file

@ -1,38 +1,45 @@
use crate::parsing_utils;
use crate::{parsing_utils, solutions::day5::range_mapping::RangeMapping};
use super::{item_mapping::ItemMapping, UnsignedNumber};
use super::mapping_layer::MappingLayer;
pub fn parse(input: &str) -> (Vec<UnsignedNumber>, Vec<Vec<ItemMapping>>) {
pub fn parse(input: &str) -> (Vec<u32>, Vec<MappingLayer>) {
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)
}
let mut blocks = parsing_utils::blocks_of_lines_seperated_by_empty_lines(input);
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::<UnsignedNumber>().unwrap())
.collect()
};
let mappings: Vec<Vec<ItemMapping>> = blocks
.map(|lines| {
lines
.into_iter()
.skip(1)
.map(|to_parse| to_parse.parse::<ItemMapping>().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())
.collect();
let mappings = blocks.into_iter().map(parse_one_layer).collect();
(seeds, mappings)
}
#[cfg(test)]
mod testing {
#[test]
fn should_parse() {
let input = include_str!("day_example_input.txt");
fn parse_input_day5() {
let input = include_str!("day5_example_input.txt");
let actual = super::parse(input);
insta::assert_debug_snapshot!(actual);
let expected_seeds: Vec<u32> = vec![79, 14, 55, 13];
assert_eq!(expected_seeds, actual.0);
insta::assert_debug_snapshot!(actual.1);
}
}

View file

@ -0,0 +1,76 @@
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq)]
pub struct RangeMapping {
source: u32,
target: u32,
range: u32,
}
#[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<Self, Self::Err> {
let mut numbers_as_3 = s.split(" ").take(3).map(|unparsed| {
unparsed
.parse::<u32>()
.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 source(&self) -> u32 {
self.source
}
pub fn target(&self) -> u32 {
self.target
}
pub fn range(&self) -> u32 {
self.range
}
}
#[cfg(test)]
mod testing {
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);
}
}

View file

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

View file

@ -0,0 +1,131 @@
---
source: src/solutions/day5/parsing.rs
expression: actual.1
---
[
MappingLayer {
label: "seed-to-soil map:",
ranges: [
RangeMapping {
source: 50,
target: 52,
range: 48,
},
RangeMapping {
source: 98,
target: 50,
range: 2,
},
],
},
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,
},
],
},
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,
},
],
},
MappingLayer {
label: "water-to-light map:",
ranges: [
RangeMapping {
source: 18,
target: 88,
range: 7,
},
RangeMapping {
source: 25,
target: 18,
range: 70,
},
],
},
MappingLayer {
label: "light-to-temperature map:",
ranges: [
RangeMapping {
source: 45,
target: 81,
range: 19,
},
RangeMapping {
source: 64,
target: 68,
range: 13,
},
RangeMapping {
source: 77,
target: 45,
range: 23,
},
],
},
MappingLayer {
label: "temperature-to-humidity map:",
ranges: [
RangeMapping {
source: 0,
target: 1,
range: 69,
},
RangeMapping {
source: 69,
target: 0,
range: 1,
},
],
},
MappingLayer {
label: "humidity-to-location map:",
ranges: [
RangeMapping {
source: 56,
target: 60,
range: 37,
},
RangeMapping {
source: 93,
target: 56,
range: 4,
},
],
},
]

View file

@ -1,172 +0,0 @@
---
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,
},
],
],
)