Solved 1 task for day 5
This commit is contained in:
parent
cb7e8465a2
commit
79b55bd23a
10 changed files with 280 additions and 79 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
/target
|
||||
/real_puzzel_input/*
|
||||
!/real_puzzel_input/.gitkeep
|
||||
|
|
60
\
60
\
|
@ -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),
|
||||
}
|
0
real_puzzel_input/.gitkeep
Normal file
0
real_puzzel_input/.gitkeep
Normal file
|
@ -2,7 +2,7 @@ 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],
|
||||
|
|
|
@ -1,15 +1,47 @@
|
|||
use mapping_layer::MappingLayer;
|
||||
|
||||
type UnsignedNumber = u64;
|
||||
mod mapping_layer;
|
||||
mod parsing;
|
||||
mod range_mapping;
|
||||
pub fn solve_task_1(input: &str) -> String {
|
||||
let _parsed = parsing::parse(input);
|
||||
todo!()
|
||||
let (seeds, layers) = parsing::parse(input);
|
||||
let location = get_lowest_locations_from(&seeds, &layers);
|
||||
location.to_string()
|
||||
}
|
||||
|
||||
fn location_of_one_point(seeds: u32, layers: &[MappingLayer]) -> u32 {
|
||||
todo!()
|
||||
fn get_lowest_locations_from(seeds: &[UnsignedNumber], layers: &[MappingLayer]) -> UnsignedNumber {
|
||||
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(¤t_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)]
|
||||
|
@ -22,8 +54,8 @@ mod testing {
|
|||
// Seed 13, soil 13, fertilizer 52, water 41, light 34, temperature 34, humidity 35, location 35.
|
||||
#[test]
|
||||
fn name() {
|
||||
fn assert_case(seeds: u32, layer: &[MappingLayer], expected: u32) {
|
||||
let actual = location_of_one_point(seeds, layer);
|
||||
fn assert_case(seeds: UnsignedNumber, layer: &[MappingLayer], expected: UnsignedNumber) {
|
||||
let actual = location_of_one_point(seeds, &layer);
|
||||
assert_eq!(expected, actual, "Given seeds: {}", seeds);
|
||||
}
|
||||
let input = parsing::parse(include_str!("day5/day5_example_input.txt"));
|
||||
|
|
|
@ -11,9 +11,59 @@ pub struct MappingLayer {
|
|||
impl MappingLayer {
|
||||
pub fn new(label: impl Into<String>, mut ranges: Vec<RangeMapping>) -> Self {
|
||||
ranges.sort_by_key(|element| element.source());
|
||||
let ranges = Self::fill_in_the_gaps(ranges);
|
||||
Self {
|
||||
label: label.into(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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 {
|
||||
const ALREADY_GOT_MAPPING_NAME: usize = 1;
|
||||
let mapping_name = *lines.first().unwrap();
|
||||
|
@ -33,12 +33,13 @@ pub fn parse(input: &str) -> (Vec<u32>, Vec<MappingLayer>) {
|
|||
|
||||
#[cfg(test)]
|
||||
mod testing {
|
||||
use crate::solutions::day5::UnsignedNumber;
|
||||
|
||||
#[test]
|
||||
fn parse_input_day5() {
|
||||
let input = include_str!("day5_example_input.txt");
|
||||
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);
|
||||
insta::assert_debug_snapshot!(actual.1);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::str::FromStr;
|
||||
use std::{str::FromStr, u32};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
use super::UnsignedNumber;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct RangeMapping {
|
||||
source: u32,
|
||||
target: u32,
|
||||
range: u32,
|
||||
source: UnsignedNumber,
|
||||
target: UnsignedNumber,
|
||||
range: UnsignedNumber,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -16,13 +18,14 @@ pub enum ParseErrorRangeMapping {
|
|||
#[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>()
|
||||
.parse::<UnsignedNumber>()
|
||||
.map_err(|_| ParseErrorRangeMapping::InvalidFormatForNumbers)
|
||||
});
|
||||
let (target, source, range) = (
|
||||
|
@ -46,20 +49,72 @@ impl FromStr for 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn range(&self) -> u32 {
|
||||
pub fn range(&self) -> UnsignedNumber {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing {
|
||||
use std::u32;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
@ -73,4 +128,29 @@ mod testing {
|
|||
let actual: RangeMapping = INPUT.parse().unwrap();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]
|
|
@ -6,6 +6,11 @@ expression: actual.1
|
|||
MappingLayer {
|
||||
label: "seed-to-soil map:",
|
||||
ranges: [
|
||||
RangeMapping {
|
||||
source: 0,
|
||||
target: 0,
|
||||
range: 50,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 50,
|
||||
target: 52,
|
||||
|
@ -16,6 +21,11 @@ expression: actual.1
|
|||
target: 50,
|
||||
range: 2,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 100,
|
||||
target: 100,
|
||||
range: 18446744073709551516,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
|
@ -36,6 +46,11 @@ expression: actual.1
|
|||
target: 37,
|
||||
range: 2,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 54,
|
||||
target: 54,
|
||||
range: 18446744073709551562,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
|
@ -61,11 +76,21 @@ expression: actual.1
|
|||
target: 49,
|
||||
range: 8,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 61,
|
||||
target: 61,
|
||||
range: 18446744073709551555,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
label: "water-to-light map:",
|
||||
ranges: [
|
||||
RangeMapping {
|
||||
source: 0,
|
||||
target: 0,
|
||||
range: 18,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 18,
|
||||
target: 88,
|
||||
|
@ -76,11 +101,21 @@ expression: actual.1
|
|||
target: 18,
|
||||
range: 70,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 95,
|
||||
target: 95,
|
||||
range: 18446744073709551521,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
label: "light-to-temperature map:",
|
||||
ranges: [
|
||||
RangeMapping {
|
||||
source: 0,
|
||||
target: 0,
|
||||
range: 45,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 45,
|
||||
target: 81,
|
||||
|
@ -96,6 +131,11 @@ expression: actual.1
|
|||
target: 45,
|
||||
range: 23,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 100,
|
||||
target: 100,
|
||||
range: 18446744073709551516,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
|
@ -111,11 +151,21 @@ expression: actual.1
|
|||
target: 0,
|
||||
range: 1,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 70,
|
||||
target: 70,
|
||||
range: 18446744073709551546,
|
||||
},
|
||||
],
|
||||
},
|
||||
MappingLayer {
|
||||
label: "humidity-to-location map:",
|
||||
ranges: [
|
||||
RangeMapping {
|
||||
source: 0,
|
||||
target: 0,
|
||||
range: 56,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 56,
|
||||
target: 60,
|
||||
|
@ -126,6 +176,11 @@ expression: actual.1
|
|||
target: 56,
|
||||
range: 4,
|
||||
},
|
||||
RangeMapping {
|
||||
source: 97,
|
||||
target: 97,
|
||||
range: 18446744073709551519,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
Loading…
Add table
Reference in a new issue