Implemented input as direct value or as file input

Implemented parsing for day5
This commit is contained in:
BoolPurist 2024-08-16 16:36:56 +02:00
parent 0bc8636003
commit cb7e8465a2
15 changed files with 567 additions and 8 deletions

55
Cargo.lock generated
View file

@ -9,6 +9,7 @@ dependencies = [
"clap",
"derive_more",
"env_logger",
"insta",
"log",
"regex",
"thiserror",
@ -118,6 +119,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys",
]
[[package]]
name = "convert_case"
version = "0.6.0"
@ -149,6 +162,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "env_filter"
version = "0.1.2"
@ -184,12 +203,42 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "insta"
version = "1.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"similar",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.156"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f43f184355eefb8d17fc948dbecf6c13be3c141f20d834ae842193a448c72a"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "log"
version = "0.4.22"
@ -249,6 +298,12 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "similar"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
[[package]]
name = "strsim"
version = "0.11.1"

View file

@ -10,3 +10,6 @@ clap = { version = "4.5.15", features = ["derive"] }
env_logger = "0.11.5"
log = "0.4.22"
thiserror = "1.0.63"
[dev-dependencies]
insta = "1.39.0"

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

0
file_input/.gitkeep Normal file
View file

View file

@ -0,0 +1,33 @@
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4

View file

@ -28,4 +28,8 @@ impl AppCliArguments {
pub fn input(&self) -> &str {
&self.input
}
pub fn read_as_file(&self) -> bool {
self.read_as_file
}
}

View file

@ -1,3 +1,4 @@
pub mod cli;
pub mod constants;
pub mod parsing_utils;
pub mod solutions;

View file

@ -1,4 +1,4 @@
use std::process::ExitCode;
use std::{fs, io, path::Path, process::ExitCode};
use advent_of_code_2023::{cli::AppCliArguments, solutions};
use clap::Parser;
@ -20,7 +20,7 @@ fn main() -> ExitCode {
}
}
fn solve_given(args: &AppCliArguments) -> Result<String, NoSolutionFound> {
fn solve_given(args: &AppCliArguments) -> Result<String, CouldNotSolveError> {
let all_solutions = solutions::create_solutions();
let found_task = {
@ -28,20 +28,34 @@ fn solve_given(args: &AppCliArguments) -> Result<String, NoSolutionFound> {
let task: u32 = args.task().into();
let found_day = all_solutions
.get(day.saturating_sub(1) as usize)
.ok_or_else(|| NoSolutionFound::DayNotFound(day))?;
.ok_or_else(|| CouldNotSolveError::DayNotFound(day))?;
found_day
.get(task.saturating_sub(1) as usize)
.ok_or_else(|| NoSolutionFound::TaskNotFound { day, task })
.ok_or_else(|| CouldNotSolveError::TaskNotFound { day, task })
}?;
let solved = (found_task)(args.input());
let puzzel_input = try_read_from_file_if_demanded(args)?;
let solved = (found_task)(&puzzel_input);
Ok(solved)
}
#[derive(Debug, Clone, Copy, Error)]
enum NoSolutionFound {
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, 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),
}

56
src/parsing_utils.rs Normal file
View file

@ -0,0 +1,56 @@
pub fn blocks_of_lines_seperated_by<'a>(
input: &'a str,
on_skip: impl Fn(&'a str) -> bool,
) -> impl Iterator<Item = Vec<&'a str>> {
let mut current_block = Vec::new();
let mut lines = input.lines();
std::iter::from_fn(move || {
while let Some(next_line) = lines.next() {
if on_skip(next_line) {
if !current_block.is_empty() {
let to_return = std::mem::take(&mut current_block);
return Some(to_return);
}
} else {
current_block.push(next_line);
}
}
if !current_block.is_empty() {
let to_return = std::mem::take(&mut current_block);
return Some(to_return);
}
None
})
}
pub fn blocks_of_lines_seperated_by_empty_lines<'a>(
input: &'a str,
) -> impl Iterator<Item = Vec<&'a str>> {
blocks_of_lines_seperated_by(input, |line| line.trim().is_empty())
}
#[cfg(test)]
mod testing {
use std::vec;
use super::*;
#[test]
fn split_blocks_by_empty_lines() {
const INPUT: &str = "
1st
2st
3st
4st
5st
";
let expected = vec![vec!["1st", "2st"], vec!["3st"], vec!["4st"], vec!["5st"]];
let actual: Vec<Vec<&str>> =
blocks_of_lines_seperated_by(INPUT, |line| line.trim().is_empty()).collect();
assert_eq!(expected, actual);
}
}

View file

@ -1,7 +1,36 @@
use mapping_layer::MappingLayer;
mod mapping_layer;
mod parsing;
mod range_mapping;
pub fn solve_task_1(input: &str) -> String {
let _parsed = parsing::parse(input);
todo!()
}
fn parse(input: &str) -> ! {
fn location_of_one_point(seeds: u32, layers: &[MappingLayer]) -> u32 {
todo!()
}
#[cfg(test)]
mod testing {
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 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);
}
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

@ -0,0 +1,33 @@
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4

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

@ -0,0 +1,45 @@
use crate::{parsing_utils, solutions::day5::range_mapping::RangeMapping};
use super::mapping_layer::MappingLayer;
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 first_block = blocks.next().unwrap();
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 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];
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

@ -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,
},
],
},
]