Solved 1 task for day 5

This commit is contained in:
BoolPurist 2024-08-16 18:23:12 +02:00
parent cb7e8465a2
commit 79b55bd23a
10 changed files with 280 additions and 79 deletions

2
.gitignore vendored
View file

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

60
\
View file

@ -1,60 +0,0 @@
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

View file

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

View file

@ -1,15 +1,47 @@
use mapping_layer::MappingLayer; use mapping_layer::MappingLayer;
type UnsignedNumber = u64;
mod mapping_layer; mod mapping_layer;
mod parsing; mod parsing;
mod range_mapping; mod range_mapping;
pub fn solve_task_1(input: &str) -> String { pub fn solve_task_1(input: &str) -> String {
let _parsed = parsing::parse(input); let (seeds, layers) = parsing::parse(input);
todo!() let location = get_lowest_locations_from(&seeds, &layers);
location.to_string()
} }
fn location_of_one_point(seeds: u32, layers: &[MappingLayer]) -> u32 { fn get_lowest_locations_from(seeds: &[UnsignedNumber], layers: &[MappingLayer]) -> UnsignedNumber {
todo!() seeds
.into_iter()
.map(|next_seed| location_of_one_point(*next_seed, layers))
.min()
.unwrap()
}
fn location_of_one_point(seed: UnsignedNumber, layers: &[MappingLayer]) -> UnsignedNumber {
let mut current_position = seed;
for next_layer in layers {
let ranges = next_layer.ranges();
let range_to_use = match ranges
.binary_search_by_key(&current_position, |extract_source| extract_source.source())
{
Ok(exact) => ranges
.get(exact)
.expect("Exact index can not be out of bounds"),
Err(not_exact) => {
let not_exact = not_exact.clamp(0, ranges.len() - 1);
let source = ranges.get(not_exact).unwrap();
let source_pos = source.source();
if current_position > source_pos {
source
} else {
ranges.get(not_exact.saturating_sub(1)).unwrap()
}
}
};
current_position = range_to_use.map_position(current_position);
}
current_position
} }
#[cfg(test)] #[cfg(test)]
@ -22,8 +54,8 @@ mod testing {
// Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34, humidity 35, location 35. // Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34, humidity 35, location 35.
#[test] #[test]
fn name() { fn name() {
fn assert_case(seeds: u32, layer: &[MappingLayer], expected: u32) { fn assert_case(seeds: UnsignedNumber, layer: &[MappingLayer], expected: UnsignedNumber) {
let actual = location_of_one_point(seeds, layer); let actual = location_of_one_point(seeds, &layer);
assert_eq!(expected, actual, "Given seeds: {}", seeds); assert_eq!(expected, actual, "Given seeds: {}", seeds);
} }
let input = parsing::parse(include_str!("day5/day5_example_input.txt")); let input = parsing::parse(include_str!("day5/day5_example_input.txt"));

View file

@ -11,9 +11,59 @@ pub struct MappingLayer {
impl MappingLayer { impl MappingLayer {
pub fn new(label: impl Into<String>, mut ranges: Vec<RangeMapping>) -> Self { pub fn new(label: impl Into<String>, mut ranges: Vec<RangeMapping>) -> Self {
ranges.sort_by_key(|element| element.source()); ranges.sort_by_key(|element| element.source());
let ranges = Self::fill_in_the_gaps(ranges);
Self { Self {
label: label.into(), label: label.into(),
ranges, ranges,
} }
} }
/// # Expected
/// It is assumed that ranges is sorted by the field `source`
pub fn fill_in_the_gaps(ranges: Vec<RangeMapping>) -> Vec<RangeMapping> {
let mut current_source_start = 0;
let mut without_gaps: Vec<RangeMapping> = Vec::new();
for next_range in ranges {
let next_source = next_range.source();
assert!(current_source_start <= next_source);
if current_source_start == next_source {
current_source_start = next_range.inclusive_end_source().saturating_add(1);
without_gaps.push(next_range);
} else {
let new_range = next_source;
without_gaps.push(RangeMapping::just_with_source(
current_source_start,
new_range,
));
current_source_start = next_range.inclusive_end_source() + 1;
without_gaps.push(next_range);
}
}
without_gaps.push(RangeMapping::source_to_max(current_source_start));
without_gaps
}
pub fn ranges(&self) -> &[RangeMapping] {
&self.ranges
}
}
#[cfg(test)]
mod testing {
use crate::solutions::day5::{mapping_layer::MappingLayer, range_mapping::RangeMapping};
#[test]
fn should_fill_in_the_gaps() {
let input = vec![
RangeMapping::new(45, 81, 19),
RangeMapping::new(64, 68, 13),
RangeMapping::new(77, 45, 23),
RangeMapping::new(110, 55, 20),
];
let actual = MappingLayer::fill_in_the_gaps(input);
insta::assert_debug_snapshot!(actual);
}
} }

View file

@ -1,8 +1,8 @@
use crate::{parsing_utils, solutions::day5::range_mapping::RangeMapping}; use crate::{parsing_utils, solutions::day5::range_mapping::RangeMapping};
use super::mapping_layer::MappingLayer; use super::{mapping_layer::MappingLayer, UnsignedNumber};
pub fn parse(input: &str) -> (Vec<u32>, Vec<MappingLayer>) { pub fn parse(input: &str) -> (Vec<UnsignedNumber>, Vec<MappingLayer>) {
fn parse_one_layer(lines: Vec<&str>) -> MappingLayer { fn parse_one_layer(lines: Vec<&str>) -> MappingLayer {
const ALREADY_GOT_MAPPING_NAME: usize = 1; const ALREADY_GOT_MAPPING_NAME: usize = 1;
let mapping_name = *lines.first().unwrap(); let mapping_name = *lines.first().unwrap();
@ -33,12 +33,13 @@ pub fn parse(input: &str) -> (Vec<u32>, Vec<MappingLayer>) {
#[cfg(test)] #[cfg(test)]
mod testing { mod testing {
use crate::solutions::day5::UnsignedNumber;
#[test] #[test]
fn parse_input_day5() { fn parse_input_day5() {
let input = include_str!("day5_example_input.txt"); let input = include_str!("day5_example_input.txt");
let actual = super::parse(input); let actual = super::parse(input);
let expected_seeds: Vec<u32> = vec![79, 14, 55, 13]; let expected_seeds: Vec<UnsignedNumber> = vec![79, 14, 55, 13];
assert_eq!(expected_seeds, actual.0); assert_eq!(expected_seeds, actual.0);
insta::assert_debug_snapshot!(actual.1); insta::assert_debug_snapshot!(actual.1);
} }

View file

@ -1,12 +1,14 @@
use std::str::FromStr; use std::{str::FromStr, u32};
use thiserror::Error; use thiserror::Error;
#[derive(Debug, PartialEq, Eq)] use super::UnsignedNumber;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct RangeMapping { pub struct RangeMapping {
source: u32, source: UnsignedNumber,
target: u32, target: UnsignedNumber,
range: u32, range: UnsignedNumber,
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -16,13 +18,14 @@ pub enum ParseErrorRangeMapping {
#[error("Every item must be a valid unsigned number")] #[error("Every item must be a valid unsigned number")]
InvalidFormatForNumbers, InvalidFormatForNumbers,
} }
impl FromStr for RangeMapping { impl FromStr for RangeMapping {
type Err = ParseErrorRangeMapping; type Err = ParseErrorRangeMapping;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut numbers_as_3 = s.split(" ").take(3).map(|unparsed| { let mut numbers_as_3 = s.split(" ").take(3).map(|unparsed| {
unparsed unparsed
.parse::<u32>() .parse::<UnsignedNumber>()
.map_err(|_| ParseErrorRangeMapping::InvalidFormatForNumbers) .map_err(|_| ParseErrorRangeMapping::InvalidFormatForNumbers)
}); });
let (target, source, range) = ( let (target, source, range) = (
@ -46,20 +49,72 @@ impl FromStr for RangeMapping {
} }
impl RangeMapping { impl RangeMapping {
pub fn source(&self) -> u32 { pub fn new(source: UnsignedNumber, target: UnsignedNumber, range: UnsignedNumber) -> Self {
Self {
source,
target,
range,
}
}
pub fn just_with_source(source: UnsignedNumber, range: UnsignedNumber) -> Self {
Self {
source,
target: source,
range,
}
}
pub fn source_to_max(source: UnsignedNumber) -> Self {
const ENSURES_SOURCE_END_IS_MAX: UnsignedNumber = 1;
let range = if source == 0 {
UnsignedNumber::MAX
} else {
UnsignedNumber::MAX - (source - ENSURES_SOURCE_END_IS_MAX)
};
Self::just_with_source(source, range)
}
pub fn source(&self) -> UnsignedNumber {
self.source self.source
} }
pub fn target(&self) -> u32 { pub fn inclusive_end_source(&self) -> UnsignedNumber {
if self.range() == UnsignedNumber::MAX {
UnsignedNumber::MAX
} else {
self.source() + (self.range() - 1)
}
}
pub fn map_position(&self, position: UnsignedNumber) -> UnsignedNumber {
let inclusive_end_source = self.inclusive_end_source();
if self.source() > position || position > inclusive_end_source {
panic!(
"Given position ({}) is outside of this range between ({}) and ({})",
position,
self.source(),
inclusive_end_source
);
}
let difference = position - self.source();
self.target() + difference
}
pub fn target(&self) -> UnsignedNumber {
self.target self.target
} }
pub fn range(&self) -> u32 { pub fn range(&self) -> UnsignedNumber {
self.range self.range
} }
} }
#[cfg(test)] #[cfg(test)]
mod testing { mod testing {
use std::u32;
use super::*; use super::*;
#[test] #[test]
@ -73,4 +128,29 @@ mod testing {
let actual: RangeMapping = INPUT.parse().unwrap(); let actual: RangeMapping = INPUT.parse().unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
#[test]
fn calc_inclusive_end_source() {
fn assert_case(given: RangeMapping, expected: UnsignedNumber) {
let actual = given.inclusive_end_source();
assert_eq!(expected, actual, "Given: {:#?}", given);
}
assert_case(RangeMapping::new(98, 50, 2), 99);
assert_case(RangeMapping::new(0, 50, 1), 0);
assert_case(RangeMapping::new(10, 50, 5), 14);
}
#[test]
fn create_from_source_to_max_integer_max_correctly() {
fn assert_case(input: RangeMapping) {
const EXPECTED: UnsignedNumber = UnsignedNumber::MAX;
let actual = input.inclusive_end_source();
assert_eq!(EXPECTED, actual, "Given: {:#?}", input);
}
assert_case(RangeMapping::source_to_max(UnsignedNumber::MAX));
assert_case(RangeMapping::source_to_max(UnsignedNumber::MAX - 1));
assert_case(RangeMapping::source_to_max(100));
assert_case(RangeMapping::source_to_max(2));
assert_case(RangeMapping::source_to_max(0));
}
} }

View file

@ -0,0 +1,41 @@
---
source: src/solutions/day5/mapping_layer.rs
expression: actual
---
[
RangeMapping {
source: 0,
target: 0,
range: 45,
},
RangeMapping {
source: 45,
target: 81,
range: 19,
},
RangeMapping {
source: 64,
target: 68,
range: 13,
},
RangeMapping {
source: 77,
target: 45,
range: 23,
},
RangeMapping {
source: 100,
target: 100,
range: 110,
},
RangeMapping {
source: 110,
target: 55,
range: 20,
},
RangeMapping {
source: 130,
target: 130,
range: 18446744073709551486,
},
]

View file

@ -6,6 +6,11 @@ expression: actual.1
MappingLayer { MappingLayer {
label: "seed-to-soil map:", label: "seed-to-soil map:",
ranges: [ ranges: [
RangeMapping {
source: 0,
target: 0,
range: 50,
},
RangeMapping { RangeMapping {
source: 50, source: 50,
target: 52, target: 52,
@ -16,6 +21,11 @@ expression: actual.1
target: 50, target: 50,
range: 2, range: 2,
}, },
RangeMapping {
source: 100,
target: 100,
range: 18446744073709551516,
},
], ],
}, },
MappingLayer { MappingLayer {
@ -36,6 +46,11 @@ expression: actual.1
target: 37, target: 37,
range: 2, range: 2,
}, },
RangeMapping {
source: 54,
target: 54,
range: 18446744073709551562,
},
], ],
}, },
MappingLayer { MappingLayer {
@ -61,11 +76,21 @@ expression: actual.1
target: 49, target: 49,
range: 8, range: 8,
}, },
RangeMapping {
source: 61,
target: 61,
range: 18446744073709551555,
},
], ],
}, },
MappingLayer { MappingLayer {
label: "water-to-light map:", label: "water-to-light map:",
ranges: [ ranges: [
RangeMapping {
source: 0,
target: 0,
range: 18,
},
RangeMapping { RangeMapping {
source: 18, source: 18,
target: 88, target: 88,
@ -76,11 +101,21 @@ expression: actual.1
target: 18, target: 18,
range: 70, range: 70,
}, },
RangeMapping {
source: 95,
target: 95,
range: 18446744073709551521,
},
], ],
}, },
MappingLayer { MappingLayer {
label: "light-to-temperature map:", label: "light-to-temperature map:",
ranges: [ ranges: [
RangeMapping {
source: 0,
target: 0,
range: 45,
},
RangeMapping { RangeMapping {
source: 45, source: 45,
target: 81, target: 81,
@ -96,6 +131,11 @@ expression: actual.1
target: 45, target: 45,
range: 23, range: 23,
}, },
RangeMapping {
source: 100,
target: 100,
range: 18446744073709551516,
},
], ],
}, },
MappingLayer { MappingLayer {
@ -111,11 +151,21 @@ expression: actual.1
target: 0, target: 0,
range: 1, range: 1,
}, },
RangeMapping {
source: 70,
target: 70,
range: 18446744073709551546,
},
], ],
}, },
MappingLayer { MappingLayer {
label: "humidity-to-location map:", label: "humidity-to-location map:",
ranges: [ ranges: [
RangeMapping {
source: 0,
target: 0,
range: 56,
},
RangeMapping { RangeMapping {
source: 56, source: 56,
target: 60, target: 60,
@ -126,6 +176,11 @@ expression: actual.1
target: 56, target: 56,
range: 4, range: 4,
}, },
RangeMapping {
source: 97,
target: 97,
range: 18446744073709551519,
},
], ],
}, },
] ]