Implemented benchmark framework
This commit is contained in:
parent
5a98c0541b
commit
854f1f1334
20 changed files with 612 additions and 79 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
@ -6,10 +6,14 @@ version = 3
|
||||||
name = "advent_of_code_2023"
|
name = "advent_of_code_2023"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colonnade",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
"ron",
|
||||||
|
"serde",
|
||||||
"solutions_advent_of_code_2023",
|
"solutions_advent_of_code_2023",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -72,6 +76,27 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.16"
|
version = "4.5.16"
|
||||||
|
@ -112,6 +137,16 @@ version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colonnade"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "833230a1f610c0f3418a80b03c387be9de15b1172b3785b702961ead07e3e475"
|
||||||
|
dependencies = [
|
||||||
|
"strip-ansi-escapes",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -297,6 +332,38 @@ version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ron"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bitflags",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.209"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.209"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
@ -313,6 +380,15 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strip-ansi-escapes"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
|
||||||
|
dependencies = [
|
||||||
|
"vte",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -374,6 +450,26 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
"vte_generate_state_changes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte_generate_state_changes"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -1,3 +0,0 @@
|
||||||
# TODO
|
|
||||||
- Implment subcommand to execute puzzle input files and print out how every long every one takes
|
|
||||||
|
|
|
@ -8,7 +8,11 @@ solutions_advent_of_code_2023 = { path = "../solutions" }
|
||||||
|
|
||||||
derive_more = { workspace = true }
|
derive_more = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
|
|
||||||
clap = { version = "4.5.15", features = ["derive"] }
|
clap = { version = "4.5.15", features = ["derive"] }
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
ron = "0.8.1"
|
||||||
|
colonnade = "1.3.3"
|
||||||
|
|
96
crates/cli/src/benchmarking.rs
Normal file
96
crates/cli/src/benchmarking.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
pub use benchmark_result::BenchmarkResult;
|
||||||
|
pub use file_to_benchmark::FileToBenchmark;
|
||||||
|
|
||||||
|
mod benchmark_result;
|
||||||
|
mod file_to_benchmark;
|
||||||
|
mod row_building;
|
||||||
|
|
||||||
|
use crate::{cli::BenchmarkCli, solving_given::solve_given, AppResult};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use colonnade::Colonnade;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
fn calc_average(sum: Duration, nanos_count: u64) -> Duration {
|
||||||
|
let average_raw = sum.div_duration_f64(Duration::from_nanos(nanos_count));
|
||||||
|
Duration::from_nanos(average_raw.round() as u64)
|
||||||
|
}
|
||||||
|
pub fn execute_benchmark(args: &BenchmarkCli) -> AppResult<String> {
|
||||||
|
let loaded = load_benchmarks(args)?;
|
||||||
|
let benchmarked = loaded
|
||||||
|
.into_iter()
|
||||||
|
.map(solve_and_keep_track_of_runtime)
|
||||||
|
.collect::<AppResult<Vec<BenchmarkResult>>>()?;
|
||||||
|
let sum: Duration = benchmarked.iter().map(|result| result.how_long()).sum();
|
||||||
|
let average = calc_average(sum, benchmarked.len() as u64);
|
||||||
|
let header = row_building::create_header();
|
||||||
|
let after_header = row_building::create_rows_for_every_solutions(benchmarked);
|
||||||
|
let table: Vec<Vec<String>> = after_header
|
||||||
|
.into_iter()
|
||||||
|
.chain(row_building::create_sum_row(sum))
|
||||||
|
.chain(row_building::create_average_row(average))
|
||||||
|
.chain(header)
|
||||||
|
.collect();
|
||||||
|
let lines = Colonnade::new(5, 100).unwrap().tabulate(table)?;
|
||||||
|
|
||||||
|
Ok(lines.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_and_keep_track_of_runtime(
|
||||||
|
(benchmark, content): (FileToBenchmark, String),
|
||||||
|
) -> AppResult<BenchmarkResult> {
|
||||||
|
let (day, task) = (benchmark.given_day(), benchmark.given_task());
|
||||||
|
let before = Instant::now();
|
||||||
|
let actual_ouput = solve_given(day, task, &content)?;
|
||||||
|
let after = Instant::now();
|
||||||
|
|
||||||
|
let how_long = after - before;
|
||||||
|
|
||||||
|
let result = BenchmarkResult::new(benchmark, actual_ouput, how_long);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_benchmarks(args: &BenchmarkCli) -> AppResult<Vec<(FileToBenchmark, String)>> {
|
||||||
|
let benchmarks = args
|
||||||
|
.files()
|
||||||
|
.iter()
|
||||||
|
.map(|path| {
|
||||||
|
let content = std::fs::read_to_string(path).map_err(|error| {
|
||||||
|
anyhow!(
|
||||||
|
"Could not read benchmark file at {:?}\n\
|
||||||
|
Details: {}",
|
||||||
|
path,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let parsed: Vec<FileToBenchmark> = ron::from_str(&content).map_err(|error| {
|
||||||
|
anyhow!(
|
||||||
|
"Content of benchmark file at ({:?}) is in an invalid ron format\n\
|
||||||
|
Details of invalid format: {}",
|
||||||
|
path,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(parsed)
|
||||||
|
})
|
||||||
|
.collect::<AppResult<Vec<Vec<FileToBenchmark>>>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
benchmarks
|
||||||
|
.into_iter()
|
||||||
|
.map(|benchmark| {
|
||||||
|
let where_to_look = benchmark.where_to_look();
|
||||||
|
let content = std::fs::read_to_string(where_to_look).map_err(|error| {
|
||||||
|
anyhow!(
|
||||||
|
"Could not read puzzle input file at {:?} for day {} and task {}\n\
|
||||||
|
Details: {}",
|
||||||
|
where_to_look,
|
||||||
|
benchmark.given_day(),
|
||||||
|
benchmark.given_task(),
|
||||||
|
error
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok((benchmark, content))
|
||||||
|
})
|
||||||
|
.collect::<AppResult<Vec<(FileToBenchmark, String)>>>()
|
||||||
|
}
|
66
crates/cli/src/benchmarking/benchmark_result.rs
Normal file
66
crates/cli/src/benchmarking/benchmark_result.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::cli::{GivenDay, GivenTask};
|
||||||
|
|
||||||
|
use super::FileToBenchmark;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BenchmarkResult {
|
||||||
|
task: GivenTask,
|
||||||
|
day: GivenDay,
|
||||||
|
how_long: Duration,
|
||||||
|
maybe_different_actual_output: Option<String>,
|
||||||
|
expected_output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BenchmarkResult {
|
||||||
|
pub fn new(benchmarking: FileToBenchmark, actual_ouput: String, how_long: Duration) -> Self {
|
||||||
|
let (day, task, expected_output) = benchmarking.into();
|
||||||
|
let maybe_different_actual_output = if actual_ouput == expected_output {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(actual_ouput)
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
task,
|
||||||
|
day,
|
||||||
|
how_long,
|
||||||
|
maybe_different_actual_output,
|
||||||
|
expected_output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matched_with_expected_output(&self) -> bool {
|
||||||
|
self.maybe_different_actual_output().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn task(&self) -> GivenTask {
|
||||||
|
self.task
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn day(&self) -> GivenDay {
|
||||||
|
self.day
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn how_long_as_string(&self) -> String {
|
||||||
|
Self::convert_duration_to_secs_and_mili_txt(self.how_long)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_different_actual_output(&self) -> Option<&str> {
|
||||||
|
self.maybe_different_actual_output.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expected_output(&self) -> &str {
|
||||||
|
&self.expected_output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn how_long(&self) -> Duration {
|
||||||
|
self.how_long
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_duration_to_secs_and_mili_txt(how_long: Duration) -> String {
|
||||||
|
let secs = how_long.as_secs();
|
||||||
|
let mili_secs = how_long.as_nanos() % 1_000_000_000;
|
||||||
|
format!("{}:{}", secs, mili_secs)
|
||||||
|
}
|
||||||
|
}
|
71
crates/cli/src/benchmarking/file_to_benchmark.rs
Normal file
71
crates/cli/src/benchmarking/file_to_benchmark.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_more::derive::Debug;
|
||||||
|
use ron::de::SpannedError;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::cli::{GivenDay, GivenTask};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FileToBenchmark {
|
||||||
|
given_day: GivenDay,
|
||||||
|
given_task: GivenTask,
|
||||||
|
where_to_look: PathBuf,
|
||||||
|
expected_output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<(GivenDay, GivenTask, String)> for FileToBenchmark {
|
||||||
|
fn into(self) -> (GivenDay, GivenTask, String) {
|
||||||
|
(self.given_day, self.given_task, self.expected_output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FileToBenchmark {
|
||||||
|
type Err = CouldNotReadFileToBenchmark;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let path = PathBuf::from(s);
|
||||||
|
let content = std::fs::read_to_string(&path).map_err(|error| {
|
||||||
|
if error.kind() == io::ErrorKind::NotFound {
|
||||||
|
CouldNotReadFileToBenchmark::NotFound(s.to_owned())
|
||||||
|
} else {
|
||||||
|
error.into()
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let parsed_content = ron::from_str(&content)?;
|
||||||
|
Ok(parsed_content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CouldNotReadFileToBenchmark {
|
||||||
|
#[error("There is not file at {0:?}")]
|
||||||
|
NotFound(String),
|
||||||
|
#[error("Coud not read file at.\nDetails: {0}")]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
#[error("File content is not in the correct format. Details: {0}")]
|
||||||
|
InvalidRon(#[from] SpannedError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileToBenchmark {
|
||||||
|
pub fn given_day(&self) -> GivenDay {
|
||||||
|
self.given_day
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn given_task(&self) -> GivenTask {
|
||||||
|
self.given_task
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn where_to_look(&self) -> &Path {
|
||||||
|
&self.where_to_look
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expected_output(&self) -> &str {
|
||||||
|
&self.expected_output
|
||||||
|
}
|
||||||
|
}
|
55
crates/cli/src/benchmarking/row_building.rs
Normal file
55
crates/cli/src/benchmarking/row_building.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::BenchmarkResult;
|
||||||
|
type MatrixReport = Vec<Vec<String>>;
|
||||||
|
|
||||||
|
pub fn create_header() -> std::iter::Once<Vec<String>> {
|
||||||
|
std::iter::once(vec![
|
||||||
|
"Day".to_string(),
|
||||||
|
"Task".to_string(),
|
||||||
|
"How long (Seconds:Mili)".to_string(),
|
||||||
|
"Expected".to_string(),
|
||||||
|
"Actual".to_string(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_sum_row(sum: Duration) -> std::iter::Once<Vec<String>> {
|
||||||
|
let passed_txt = BenchmarkResult::convert_duration_to_secs_and_mili_txt(sum);
|
||||||
|
std::iter::once(vec![
|
||||||
|
"Total".to_string(),
|
||||||
|
"Total".to_string(),
|
||||||
|
passed_txt,
|
||||||
|
"-".to_string(),
|
||||||
|
"-".to_string(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_average_row(average: Duration) -> std::iter::Once<Vec<String>> {
|
||||||
|
let passed_txt = BenchmarkResult::convert_duration_to_secs_and_mili_txt(average);
|
||||||
|
std::iter::once(vec![
|
||||||
|
"Average".to_string(),
|
||||||
|
"Average".to_string(),
|
||||||
|
passed_txt,
|
||||||
|
"-".to_string(),
|
||||||
|
"-".to_string(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_rows_for_every_solutions(loaded: Vec<BenchmarkResult>) -> MatrixReport {
|
||||||
|
let after_header = loaded
|
||||||
|
.into_iter()
|
||||||
|
.map(|result| {
|
||||||
|
vec![
|
||||||
|
result.day().to_string(),
|
||||||
|
result.task().to_string(),
|
||||||
|
result.how_long_as_string(),
|
||||||
|
result.expected_output().to_string(),
|
||||||
|
result
|
||||||
|
.maybe_different_actual_output()
|
||||||
|
.map(|unowned| unowned.to_string())
|
||||||
|
.unwrap_or_else(|| result.expected_output().to_string()),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
after_header
|
||||||
|
}
|
|
@ -1,35 +1,29 @@
|
||||||
use clap::Parser;
|
pub use benchmark_cli::BenchmarkCli;
|
||||||
mod given_day;
|
use clap::{Parser, Subcommand};
|
||||||
mod given_task;
|
pub use cli_solution_to_solve::CliSolutionToSolve;
|
||||||
|
|
||||||
pub use given_day::GivenDay;
|
pub use given_day::GivenDay;
|
||||||
pub use given_task::GivenTask;
|
pub use given_task::GivenTask;
|
||||||
|
|
||||||
|
mod benchmark_cli;
|
||||||
|
mod cli_solution_to_solve;
|
||||||
|
mod given_day;
|
||||||
|
|
||||||
|
mod given_task;
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum AppCliSubcommands {
|
||||||
|
Solve(CliSolutionToSolve),
|
||||||
|
Benchmark(BenchmarkCli),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct AppCliArguments {
|
pub struct AppCliArgs {
|
||||||
#[arg(short, long)]
|
#[command(subcommand)]
|
||||||
day: GivenDay,
|
sub_command: AppCliSubcommands,
|
||||||
#[arg(short, long)]
|
|
||||||
task: GivenTask,
|
|
||||||
#[arg(short, long)]
|
|
||||||
read_as_file: bool,
|
|
||||||
input: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppCliArguments {
|
impl AppCliArgs {
|
||||||
pub fn day(&self) -> GivenDay {
|
pub fn sub_command(&self) -> &AppCliSubcommands {
|
||||||
self.day
|
&self.sub_command
|
||||||
}
|
|
||||||
|
|
||||||
pub fn task(&self) -> GivenTask {
|
|
||||||
self.task
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input(&self) -> &str {
|
|
||||||
&self.input
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_as_file(&self) -> bool {
|
|
||||||
self.read_as_file
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
crates/cli/src/cli/benchmark_cli.rs
Normal file
14
crates/cli/src/cli/benchmark_cli.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct BenchmarkCli {
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BenchmarkCli {
|
||||||
|
pub fn files(&self) -> &[PathBuf] {
|
||||||
|
&self.files
|
||||||
|
}
|
||||||
|
}
|
32
crates/cli/src/cli/cli_solution_to_solve.rs
Normal file
32
crates/cli/src/cli/cli_solution_to_solve.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use super::{GivenDay, GivenTask};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct CliSolutionToSolve {
|
||||||
|
#[arg(short, long)]
|
||||||
|
day: GivenDay,
|
||||||
|
#[arg(short, long)]
|
||||||
|
task: GivenTask,
|
||||||
|
#[arg(short, long)]
|
||||||
|
read_as_file: bool,
|
||||||
|
input: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliSolutionToSolve {
|
||||||
|
pub fn day(&self) -> GivenDay {
|
||||||
|
self.day
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn task(&self) -> GivenTask {
|
||||||
|
self.task
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input(&self) -> &str {
|
||||||
|
&self.input
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_as_file(&self) -> bool {
|
||||||
|
self.read_as_file
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{str::FromStr, sync::Arc};
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use derive_more::derive::Into;
|
use derive_more::derive::{Display, Into};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
|
@ -16,7 +17,7 @@ pub enum InvalidGivenDayError {
|
||||||
InvalidRange(u32),
|
InvalidRange(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Into, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Into, PartialEq, Eq, Serialize, Deserialize, Display)]
|
||||||
pub struct GivenDay(u32);
|
pub struct GivenDay(u32);
|
||||||
|
|
||||||
impl GivenDay {
|
impl GivenDay {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{str::FromStr, sync::Arc};
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use derive_more::derive::Into;
|
use derive_more::derive::{Display, Into};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
|
@ -18,7 +19,7 @@ pub enum InvalidGivenTaskError {
|
||||||
InvalidRange(u32),
|
InvalidRange(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Into, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Into, Clone, PartialEq, Eq, Serialize, Deserialize, Display)]
|
||||||
pub struct GivenTask(u32);
|
pub struct GivenTask(u32);
|
||||||
|
|
||||||
impl GivenTask {
|
impl GivenTask {
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
pub mod benchmarking;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod solutions;
|
pub mod solutions;
|
||||||
|
pub mod solving_given;
|
||||||
|
|
||||||
|
pub type AppError = anyhow::Error;
|
||||||
|
pub type AppResult<T = ()> = anyhow::Result<T>;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
use std::{fs, io, path::Path, process::ExitCode};
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use advent_of_code_2023::{cli::AppCliArguments, solutions};
|
use advent_of_code_2023::{
|
||||||
|
benchmarking::execute_benchmark,
|
||||||
|
cli::{AppCliArgs, AppCliSubcommands},
|
||||||
|
solving_given::solve_given_from_cli,
|
||||||
|
AppResult,
|
||||||
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let args = AppCliArguments::parse();
|
let args = AppCliArgs::parse();
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let solution = solve_given(&args);
|
let solution = handle_command(&args);
|
||||||
match solution {
|
match solution {
|
||||||
Ok(found_solution) => {
|
Ok(found_solution) => {
|
||||||
println!("{}", found_solution);
|
println!("{}", found_solution);
|
||||||
|
@ -21,42 +24,9 @@ fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solve_given(args: &AppCliArguments) -> Result<String, CouldNotSolveError> {
|
fn handle_command(args: &AppCliArgs) -> AppResult<String> {
|
||||||
let all_solutions = solutions::create_solutions();
|
match args.sub_command() {
|
||||||
|
AppCliSubcommands::Solve(to_solve) => solve_given_from_cli(to_solve),
|
||||||
let found_task = {
|
AppCliSubcommands::Benchmark(benchmark) => execute_benchmark(benchmark),
|
||||||
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 puzzel_input = try_read_from_file_if_demanded(args)?;
|
|
||||||
let solved = (found_task)(&puzzel_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, 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),
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use solutions_advent_of_code_2023::day5;
|
use solutions_advent_of_code_2023::day5;
|
||||||
use solutions_advent_of_code_2023::day6;
|
use solutions_advent_of_code_2023::day6;
|
||||||
use solutions_advent_of_code_2023::day7;
|
use solutions_advent_of_code_2023::day7;
|
||||||
|
|
||||||
pub fn create_solutions() -> Vec<Vec<fn(&str) -> String>> {
|
pub type SolutionMatrix = Vec<Vec<fn(&str) -> String>>;
|
||||||
|
pub static ALL_SOLUTIONS: LazyLock<SolutionMatrix> = LazyLock::new(|| create_solutions());
|
||||||
|
fn create_solutions() -> SolutionMatrix {
|
||||||
vec![
|
vec![
|
||||||
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],
|
||||||
|
|
51
crates/cli/src/solving_given.rs
Normal file
51
crates/cli/src/solving_given.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use std::{fs, io, path::Path};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cli::{CliSolutionToSolve, GivenDay, GivenTask},
|
||||||
|
solutions::ALL_SOLUTIONS,
|
||||||
|
AppResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn solve_given(given_day: GivenDay, given_task: GivenTask, content: &str) -> AppResult<String> {
|
||||||
|
let found_task = {
|
||||||
|
let day: u32 = given_day.into();
|
||||||
|
let task: u32 = given_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)(content);
|
||||||
|
Ok(solved)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve_given_from_cli(args: &CliSolutionToSolve) -> AppResult<String> {
|
||||||
|
let puzzel_input = try_read_from_file_if_demanded(args)?;
|
||||||
|
let output = solve_given(args.day(), args.task(), &puzzel_input)?;
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_from_file_if_demanded(args: &CliSolutionToSolve) -> 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),
|
||||||
|
}
|
38
file_input/example_benchmarks.ron
Normal file
38
file_input/example_benchmarks.ron
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
[
|
||||||
|
(
|
||||||
|
given_day: GivenDay(5),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "file_input/day5_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(5),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "file_input/day5_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(6),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "file_input/day6_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(6),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "file_input/day6_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(7),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "file_input/day7_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(7),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "file_input/day7_example.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
]
|
38
real_benchmark.ron
Normal file
38
real_benchmark.ron
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
[
|
||||||
|
(
|
||||||
|
given_day: GivenDay(5),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "real_puzzel_input/day5_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(5),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "real_puzzel_input/day5_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(6),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "real_puzzel_input/day6_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(6),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "real_puzzel_input/day6_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(7),
|
||||||
|
given_task: GivenTask(1),
|
||||||
|
where_to_look: "real_puzzel_input/day7_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
given_day: GivenDay(7),
|
||||||
|
given_task: GivenTask(2),
|
||||||
|
where_to_look: "real_puzzel_input/day7_real.txt",
|
||||||
|
expected_output: "42",
|
||||||
|
),
|
||||||
|
]
|
Loading…
Add table
Reference in a new issue