Compare commits
No commits in common. "603ee1827d494a83ef86851e27b49f6320db9fdf" and "4970f247f3d1c63e274abcc4345b3ea03964d52e" have entirely different histories.
603ee1827d
...
4970f247f3
19 changed files with 162 additions and 512 deletions
207
Cargo.lock
generated
207
Cargo.lock
generated
|
@ -8,10 +8,10 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"colonnade",
|
||||
"derive_more",
|
||||
"env_logger",
|
||||
"log",
|
||||
"prettytable-rs",
|
||||
"ron",
|
||||
"serde",
|
||||
"solutions_advent_of_code_2023",
|
||||
|
@ -97,12 +97,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.16"
|
||||
|
@ -143,6 +137,16 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.2"
|
||||
|
@ -155,7 +159,7 @@ version = "0.15.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||
dependencies = [
|
||||
"encode_unicode 0.3.6",
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
|
@ -170,27 +174,6 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "1.0.0"
|
||||
|
@ -213,39 +196,12 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.2"
|
||||
|
@ -269,29 +225,12 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -310,29 +249,12 @@ dependencies = [
|
|||
"similar",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
@ -345,16 +267,6 @@ version = "0.2.158"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
|
@ -373,20 +285,6 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "prettytable-rs"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"encode_unicode 1.0.0",
|
||||
"is-terminal",
|
||||
"lazy_static",
|
||||
"term",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
|
@ -405,17 +303,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
|
@ -457,18 +344,6 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.209"
|
||||
|
@ -505,6 +380,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
|
@ -522,17 +406,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"rustversion",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
|
@ -565,12 +438,6 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.5"
|
||||
|
@ -584,32 +451,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
|
|
|
@ -15,4 +15,4 @@ env_logger = "0.11.5"
|
|||
log = "0.4.22"
|
||||
anyhow = "1.0.86"
|
||||
ron = "0.8.1"
|
||||
prettytable-rs = "0.10.0"
|
||||
colonnade = "1.3.3"
|
||||
|
|
|
@ -1,52 +1,43 @@
|
|||
pub use conducted_benchmark::ConductedBenchmark;
|
||||
pub use benchmark_result::BenchmarkResult;
|
||||
pub use file_to_benchmark::FileToBenchmark;
|
||||
|
||||
use prettytable::Table;
|
||||
|
||||
mod conducted_benchmark;
|
||||
mod benchmark_result;
|
||||
mod file_to_benchmark;
|
||||
mod row_builder;
|
||||
mod row_building;
|
||||
|
||||
use crate::{
|
||||
cli::BenchmarkCli,
|
||||
solving_given::{solve_given, NoSolutionFound},
|
||||
AppError, AppResult,
|
||||
};
|
||||
use crate::{cli::BenchmarkCli, solving_given::solve_given, AppResult};
|
||||
use anyhow::anyhow;
|
||||
use colonnade::Colonnade;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub type BenchmarkResult = Result<ConductedBenchmark, NoSolutionFound>;
|
||||
|
||||
pub fn execute_benchmark(args: &BenchmarkCli) -> AppResult<Table> {
|
||||
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);
|
||||
|
||||
let mut sum: Duration = Duration::ZERO;
|
||||
let mut count: u64 = 0;
|
||||
let after_header = row_building::create_rows_for_every_solutions(benchmarked, |to_add| {
|
||||
sum += to_add;
|
||||
count += 1;
|
||||
});
|
||||
let average = calc_average(sum, count);
|
||||
|
||||
let mut table = Table::new();
|
||||
let rows = after_header
|
||||
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(row_building::create_header());
|
||||
for next_row in rows {
|
||||
table.add_row(next_row);
|
||||
}
|
||||
.chain(header)
|
||||
.collect();
|
||||
let lines = Colonnade::new(5, 100).unwrap().tabulate(table)?;
|
||||
|
||||
Ok(table)
|
||||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn solve_and_keep_track_of_runtime(
|
||||
(benchmark, content): (FileToBenchmark, String),
|
||||
) -> Result<ConductedBenchmark, NoSolutionFound> {
|
||||
) -> AppResult<BenchmarkResult> {
|
||||
let (day, task) = (benchmark.given_day(), benchmark.given_task());
|
||||
let before = Instant::now();
|
||||
let actual_ouput = solve_given(day, task, &content)?;
|
||||
|
@ -54,14 +45,15 @@ fn solve_and_keep_track_of_runtime(
|
|||
|
||||
let how_long = after - before;
|
||||
|
||||
let result = ConductedBenchmark::new(benchmark, actual_ouput, how_long);
|
||||
let result = BenchmarkResult::new(benchmark, actual_ouput, how_long);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn load_benchmarks(args: &BenchmarkCli) -> AppResult<Vec<(FileToBenchmark, String)>> {
|
||||
args.files()
|
||||
let benchmarks = args
|
||||
.files()
|
||||
.iter()
|
||||
.flat_map(|path| {
|
||||
.map(|path| {
|
||||
let content = std::fs::read_to_string(path).map_err(|error| {
|
||||
anyhow!(
|
||||
"Could not read benchmark file at {:?}\n\
|
||||
|
@ -78,9 +70,14 @@ fn load_benchmarks(args: &BenchmarkCli) -> AppResult<Vec<(FileToBenchmark, Strin
|
|||
error
|
||||
)
|
||||
})?;
|
||||
Ok::<_, AppError>(parsed)
|
||||
Ok(parsed)
|
||||
})
|
||||
.flatten()
|
||||
.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| {
|
||||
|
@ -95,10 +92,5 @@ fn load_benchmarks(args: &BenchmarkCli) -> AppResult<Vec<(FileToBenchmark, Strin
|
|||
})?;
|
||||
Ok((benchmark, content))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
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)
|
||||
.collect::<AppResult<Vec<(FileToBenchmark, String)>>>()
|
||||
}
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::cli::{GivenDay, GivenTask};
|
||||
|
||||
use super::FileToBenchmark;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConductedBenchmark {
|
||||
pub struct BenchmarkResult {
|
||||
task: GivenTask,
|
||||
day: GivenDay,
|
||||
how_long: Duration,
|
||||
maybe_different_actual_output: Option<String>,
|
||||
expected_output: String,
|
||||
path_of_input: PathBuf,
|
||||
}
|
||||
|
||||
impl ConductedBenchmark {
|
||||
impl BenchmarkResult {
|
||||
pub fn new(benchmarking: FileToBenchmark, actual_ouput: String, how_long: Duration) -> Self {
|
||||
let (day, task, expected_output, path_of_input) = benchmarking.into();
|
||||
let (day, task, expected_output) = benchmarking.into();
|
||||
let maybe_different_actual_output = if actual_ouput == expected_output {
|
||||
None
|
||||
} else {
|
||||
|
@ -31,7 +27,6 @@ impl ConductedBenchmark {
|
|||
how_long,
|
||||
maybe_different_actual_output,
|
||||
expected_output,
|
||||
path_of_input,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,8 +63,4 @@ impl ConductedBenchmark {
|
|||
let mili_secs = how_long.as_nanos() % 1_000_000_000;
|
||||
format!("{}:{}", secs, mili_secs)
|
||||
}
|
||||
|
||||
pub fn path_of_input(&self) -> &Path {
|
||||
&self.path_of_input
|
||||
}
|
||||
}
|
|
@ -19,14 +19,9 @@ pub struct FileToBenchmark {
|
|||
expected_output: String,
|
||||
}
|
||||
|
||||
impl From<FileToBenchmark> for (GivenDay, GivenTask, String, PathBuf) {
|
||||
impl From<FileToBenchmark> for (GivenDay, GivenTask, String) {
|
||||
fn from(val: FileToBenchmark) -> Self {
|
||||
(
|
||||
val.given_day,
|
||||
val.given_task,
|
||||
val.expected_output,
|
||||
val.where_to_look,
|
||||
)
|
||||
(val.given_day, val.given_task, val.expected_output)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
use std::{borrow::Cow, path::PathBuf, time::Duration};
|
||||
|
||||
const AVERAGE: &str = "Average";
|
||||
const TOTAL: &str = "total";
|
||||
|
||||
use prettytable::{color, Row};
|
||||
|
||||
use crate::{
|
||||
benchmarking::ConductedBenchmark,
|
||||
cli::{GivenDay, GivenTask},
|
||||
constants,
|
||||
solving_given::NoSolutionFound,
|
||||
};
|
||||
|
||||
type OptionalContent = Option<Cow<'static, str>>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RowBuilder {
|
||||
day: OptionalContent,
|
||||
task: OptionalContent,
|
||||
taken_time: OptionalContent,
|
||||
actual_result: OptionalContent,
|
||||
expected_result: OptionalContent,
|
||||
path_to_input: OptionalContent,
|
||||
}
|
||||
|
||||
impl From<NoSolutionFound> for RowBuilder {
|
||||
fn from(value: NoSolutionFound) -> Self {
|
||||
match value {
|
||||
NoSolutionFound::DayNotFound(day) => Self::no_day_found(day),
|
||||
NoSolutionFound::TaskNotFound { day, task } => Self::no_task_found(day, task),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConductedBenchmark> for RowBuilder {
|
||||
fn from(value: ConductedBenchmark) -> Self {
|
||||
Self::new(
|
||||
value.day(),
|
||||
value.task(),
|
||||
value.how_long(),
|
||||
value
|
||||
.maybe_different_actual_output()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_default(),
|
||||
value.expected_output().to_string(),
|
||||
value.path_of_input().to_path_buf(),
|
||||
)
|
||||
}
|
||||
}
|
||||
fn into_borrowed_cell(some_thing: &'static str) -> OptionalContent {
|
||||
Some(Cow::Borrowed(some_thing))
|
||||
}
|
||||
|
||||
fn into_owned_cell(some_thing: impl ToString) -> OptionalContent {
|
||||
Some(Cow::Owned(some_thing.to_string()))
|
||||
}
|
||||
fn into_owned_cell_with<T>(some_thing: T, on_convert: impl Fn(T) -> String) -> OptionalContent {
|
||||
Some(Cow::Owned(on_convert(some_thing)))
|
||||
}
|
||||
impl RowBuilder {
|
||||
pub fn new(
|
||||
day: GivenDay,
|
||||
task: GivenTask,
|
||||
taken_time: Duration,
|
||||
actual_result: String,
|
||||
expected_result: String,
|
||||
path: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
day: into_owned_cell(day),
|
||||
task: into_owned_cell(task),
|
||||
taken_time: into_owned_cell_with(
|
||||
taken_time,
|
||||
ConductedBenchmark::convert_duration_to_secs_and_mili_txt,
|
||||
),
|
||||
actual_result: into_owned_cell(actual_result),
|
||||
expected_result: into_owned_cell(expected_result),
|
||||
path_to_input: into_owned_cell_with(path, |path| path.to_string_lossy().to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_day_found(wrong_day: GivenDay) -> Self {
|
||||
let day = into_owned_cell_with(wrong_day, |to_convert| {
|
||||
format!("Solution found for day {}", to_convert)
|
||||
});
|
||||
Self {
|
||||
day,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_task_found(right_day: GivenDay, wrong_task: GivenTask) -> Self {
|
||||
let day = into_owned_cell(right_day);
|
||||
let task = into_owned_cell_with(wrong_task, |to_convert| {
|
||||
format!("No solution found for task {}", to_convert)
|
||||
});
|
||||
Self {
|
||||
day,
|
||||
task,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header() -> Self {
|
||||
Self {
|
||||
day: into_borrowed_cell("Day"),
|
||||
task: into_borrowed_cell("Task"),
|
||||
taken_time: into_borrowed_cell("How long (Seconds:Nano)"),
|
||||
actual_result: into_borrowed_cell("Actual"),
|
||||
expected_result: into_borrowed_cell("Expected"),
|
||||
path_to_input: into_borrowed_cell("Used input file"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn average(average: Duration) -> Self {
|
||||
Self::new_aggreate(AVERAGE, average)
|
||||
}
|
||||
|
||||
pub fn total(total: Duration) -> Self {
|
||||
Self::new_aggreate(TOTAL, total)
|
||||
}
|
||||
|
||||
fn this_or_placeholder(value: OptionalContent) -> prettytable::Cell {
|
||||
let value = value.unwrap_or(Cow::Borrowed(constants::PLACEHOLDER_IN_BENCHMARK_REPORTS));
|
||||
prettytable::Cell::new(&value)
|
||||
}
|
||||
fn color_red_if_non_static_value(actual_result: OptionalContent) -> prettytable::Cell {
|
||||
let has_different_acutual_than_expected_and_is_no_label = actual_result
|
||||
.as_ref()
|
||||
.is_some_and(|might_be_borrow| matches!(might_be_borrow, Cow::Owned(_)));
|
||||
let tmp = Self::this_or_placeholder(actual_result);
|
||||
if has_different_acutual_than_expected_and_is_no_label {
|
||||
tmp.with_style(prettytable::Attr::ForegroundColor(color::RED))
|
||||
} else {
|
||||
tmp
|
||||
}
|
||||
}
|
||||
pub fn into_row(self) -> Row {
|
||||
let might_colored_red_actual = Self::color_red_if_non_static_value(self.actual_result);
|
||||
|
||||
let (day, task, taken_time, actual_result, expected_result, path) = (
|
||||
Self::this_or_placeholder(self.day),
|
||||
Self::this_or_placeholder(self.task),
|
||||
Self::this_or_placeholder(self.taken_time),
|
||||
might_colored_red_actual,
|
||||
Self::this_or_placeholder(self.expected_result),
|
||||
Self::this_or_placeholder(self.path_to_input),
|
||||
);
|
||||
|
||||
prettytable::Row::new(vec![
|
||||
day,
|
||||
task,
|
||||
taken_time,
|
||||
actual_result,
|
||||
expected_result,
|
||||
path,
|
||||
])
|
||||
}
|
||||
|
||||
fn new_aggreate(label: &str, aggregate: Duration) -> Self {
|
||||
let (day, task) = (into_owned_cell(label), into_owned_cell(label));
|
||||
let taken_time = into_owned_cell_with(
|
||||
aggregate,
|
||||
ConductedBenchmark::convert_duration_to_secs_and_mili_txt,
|
||||
);
|
||||
Self {
|
||||
day,
|
||||
task,
|
||||
taken_time,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +1,54 @@
|
|||
pub type MatrixReport = Vec<Row>;
|
||||
pub type SingleRow = std::iter::Once<Row>;
|
||||
|
||||
use prettytable::Row;
|
||||
|
||||
use super::{row_builder::RowBuilder, BenchmarkResult};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn create_header() -> SingleRow {
|
||||
std::iter::once(RowBuilder::header().into_row())
|
||||
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) -> SingleRow {
|
||||
std::iter::once(RowBuilder::total(sum).into_row())
|
||||
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) -> SingleRow {
|
||||
std::iter::once(RowBuilder::average(average).into_row())
|
||||
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: impl IntoIterator<Item = BenchmarkResult>,
|
||||
mut on_ok: impl FnMut(Duration),
|
||||
) -> MatrixReport {
|
||||
pub fn create_rows_for_every_solutions(loaded: Vec<BenchmarkResult>) -> MatrixReport {
|
||||
loaded
|
||||
.into_iter()
|
||||
.map(|result| {
|
||||
result
|
||||
.inspect(|on_success| on_ok(on_success.how_long()))
|
||||
.map(RowBuilder::from)
|
||||
.unwrap_or_else(RowBuilder::from)
|
||||
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()),
|
||||
]
|
||||
})
|
||||
.map(RowBuilder::into_row)
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pub const MAX_DAY: u32 = 25;
|
||||
pub const MAX_TASK: u32 = 2;
|
||||
pub const PLACEHOLDER_IN_BENCHMARK_REPORTS: &str = "-";
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use prettytable::Table;
|
||||
|
||||
pub mod benchmarking;
|
||||
pub mod cli;
|
||||
pub mod constants;
|
||||
|
@ -10,27 +6,3 @@ pub mod solving_given;
|
|||
|
||||
pub type AppError = anyhow::Error;
|
||||
pub type AppResult<T = ()> = anyhow::Result<T>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppOutput {
|
||||
JustString(String),
|
||||
PrettyTable(Table),
|
||||
}
|
||||
|
||||
impl AppOutput {
|
||||
pub fn print_std_out(&self) {
|
||||
match self {
|
||||
AppOutput::JustString(string) => println!("{}", string),
|
||||
AppOutput::PrettyTable(table) => table.printstd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AppOutput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AppOutput::JustString(string) => f.write_str(string),
|
||||
AppOutput::PrettyTable(table) => f.write_str(&table.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use advent_of_code_2023::{
|
|||
benchmarking::execute_benchmark,
|
||||
cli::{AppCliArgs, AppCliSubcommands},
|
||||
solving_given::solve_given_from_cli,
|
||||
AppOutput, AppResult,
|
||||
AppResult,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
|
@ -14,7 +14,7 @@ fn main() -> ExitCode {
|
|||
let solution = handle_command(&args);
|
||||
match solution {
|
||||
Ok(found_solution) => {
|
||||
found_solution.print_std_out();
|
||||
println!("{}", found_solution);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(error) => {
|
||||
|
@ -24,13 +24,9 @@ fn main() -> ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_command(args: &AppCliArgs) -> AppResult<AppOutput> {
|
||||
fn handle_command(args: &AppCliArgs) -> AppResult<String> {
|
||||
match args.sub_command() {
|
||||
AppCliSubcommands::Solve(to_solve) => {
|
||||
solve_given_from_cli(to_solve).map(AppOutput::JustString)
|
||||
}
|
||||
AppCliSubcommands::Benchmark(benchmark) => {
|
||||
execute_benchmark(benchmark).map(AppOutput::PrettyTable)
|
||||
}
|
||||
AppCliSubcommands::Solve(to_solve) => solve_given_from_cli(to_solve),
|
||||
AppCliSubcommands::Benchmark(benchmark) => execute_benchmark(benchmark),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,16 @@ use crate::{
|
|||
AppResult,
|
||||
};
|
||||
|
||||
pub fn solve_given(
|
||||
given_day: GivenDay,
|
||||
given_task: GivenTask,
|
||||
content: &str,
|
||||
) -> Result<String, NoSolutionFound> {
|
||||
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(NoSolutionFound::DayNotFound(given_day))?;
|
||||
.ok_or_else(|| CouldNotSolveError::DayNotFound(day))?;
|
||||
found_day
|
||||
.get(task.saturating_sub(1) as usize)
|
||||
.ok_or(NoSolutionFound::TaskNotFound {
|
||||
day: given_day,
|
||||
task: given_task,
|
||||
})
|
||||
.ok_or_else(|| CouldNotSolveError::TaskNotFound { day, task })
|
||||
}?;
|
||||
|
||||
let solved = (found_task)(content);
|
||||
|
@ -47,17 +40,11 @@ fn try_read_from_file_if_demanded(args: &CliSolutionToSolve) -> io::Result<Strin
|
|||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CouldNotSolveError {
|
||||
#[error("{0}")]
|
||||
NotFound(#[from] NoSolutionFound),
|
||||
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),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NoSolutionFound {
|
||||
#[error("There is no solution for the day {0}")]
|
||||
DayNotFound(GivenDay),
|
||||
#[error("There is not solution for task {task} under the day {day}")]
|
||||
TaskNotFound { day: GivenDay, task: GivenTask },
|
||||
}
|
||||
|
|
|
@ -21,9 +21,12 @@ fn min_location_from_given_single_seeds(
|
|||
let mut current_seed = *next_seed;
|
||||
for next_layer in layers {
|
||||
for next_mapping in next_layer {
|
||||
if let Some(found_mapping) = next_mapping.map_point(current_seed) {
|
||||
current_seed = found_mapping;
|
||||
break;
|
||||
match next_mapping.map_point(current_seed) {
|
||||
Some(found_mapping) => {
|
||||
current_seed = found_mapping;
|
||||
break;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +119,7 @@ fn combine_single_to_ranges_for_seeds(single_seeds: &[UnsignedNumber]) -> Vec<Se
|
|||
assert!(single_seeds.len() % 2 == 0);
|
||||
single_seeds
|
||||
.chunks(2)
|
||||
.into_iter()
|
||||
.map(|array| match array {
|
||||
[start, end] => {
|
||||
let start = *start;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::usize;
|
||||
|
||||
mod parsing;
|
||||
mod race_record;
|
||||
|
||||
|
@ -9,12 +11,12 @@ pub fn solve_task_1(input: &str) -> String {
|
|||
|
||||
for next_record in parsed {
|
||||
how_many_times_won *= all_rounds_of_durations(next_record.time())
|
||||
.into_iter()
|
||||
.filter(|&distance| distance > next_record.reached_distance())
|
||||
.count() as NumericValue;
|
||||
}
|
||||
how_many_times_won.to_string()
|
||||
}
|
||||
|
||||
pub fn solve_task_2(input: &str) -> String {
|
||||
let parsed = parsing::parsing_part_2(input);
|
||||
|
||||
|
@ -27,6 +29,7 @@ pub fn solve_task_2(input: &str) -> String {
|
|||
|
||||
const FIRST_CYCLE_NO_DISTANCE: usize = 1;
|
||||
for (index, next_distance) in all_rounds_of_durations(parsed.time())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.skip(FIRST_CYCLE_NO_DISTANCE)
|
||||
{
|
||||
|
|
|
@ -43,7 +43,7 @@ fn categorize_and_sort_with_a_joker(parsed: Vec<DealtHand>, joker: char) -> Vec<
|
|||
|
||||
fn calc_total_winning(sorted: &[CategorizedHand]) -> usize {
|
||||
sorted
|
||||
.iter()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, hand)| {
|
||||
let rank = index.saturating_add(1);
|
||||
|
|
|
@ -2,6 +2,7 @@ mod joker_ordered;
|
|||
pub use joker_ordered::JokerOrdered;
|
||||
|
||||
use crate::day7::second_ordering;
|
||||
use std::usize;
|
||||
|
||||
use super::{dealt_hand::DealtHand, hand_kind::HandKind, second_ordering::StrengthOfSymbols};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::str::FromStr;
|
||||
use std::{str::FromStr, usize};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ impl From<(&'_ str, char)> for HandKind {
|
|||
}
|
||||
}
|
||||
|
||||
let duplicated_counted = calc_duplicate_counted(characters, |next| next != joker_char);
|
||||
let duplicated_counted = calc_duplicate_counted(&characters, |next| next != joker_char);
|
||||
|
||||
let mut current_kind = choose_kind_from(&duplicated_counted);
|
||||
let mut joker_count = get_joker_count(joker_char, characters);
|
||||
|
@ -61,7 +61,7 @@ impl From<(&'_ str, char)> for HandKind {
|
|||
|
||||
impl From<&'_ str> for HandKind {
|
||||
fn from(value: &'_ str) -> Self {
|
||||
let duplicated_counted = calc_duplicate_counted(value, |_| true);
|
||||
let duplicated_counted = calc_duplicate_counted(&value, |_| true);
|
||||
choose_kind_from(&duplicated_counted)
|
||||
}
|
||||
}
|
||||
|
@ -78,13 +78,13 @@ fn calc_duplicate_counted(
|
|||
if on_consider_next_char(next_char) {
|
||||
count_of_same
|
||||
.entry(next_char)
|
||||
.and_modify(|count| *count += 1)
|
||||
.and_modify(|count| *count = *count + 1)
|
||||
.or_insert(FIRST_ENCOUNTERED);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut to_sort: Vec<usize> = count_of_same.into_values().collect();
|
||||
let mut to_sort: Vec<usize> = count_of_same.into_iter().map(|(_, count)| count).collect();
|
||||
to_sort.sort_by_key(|&count| Reverse(count));
|
||||
to_sort
|
||||
}
|
||||
|
@ -119,9 +119,9 @@ fn choose_kind_from(to_choose_from: &[usize]) -> HandKind {
|
|||
Some(kind)
|
||||
}
|
||||
|
||||
calc_kind(to_choose_from).unwrap_or_else(|| {
|
||||
|
||||
HandKind::default()
|
||||
calc_kind(&to_choose_from).unwrap_or_else(|| {
|
||||
let default_value = HandKind::default();
|
||||
default_value
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cmp::Ordering, collections::HashMap, ops::RangeInclusive, sync::LazyLock};
|
||||
use std::{cmp::Ordering, collections::HashMap, ops::RangeInclusive, sync::LazyLock, usize};
|
||||
|
||||
pub const JOKER: char = 'J';
|
||||
static LETTERS_WITHOUT_JOKER: &[char] = &['T', 'Q', 'K', 'A'];
|
||||
|
@ -8,14 +8,17 @@ const NUMBERS: RangeInclusive<u32> = 0..=9;
|
|||
pub type StrengthOfSymbols = HashMap<char, usize>;
|
||||
|
||||
fn numbers_in_chars() -> impl Iterator<Item = char> {
|
||||
NUMBERS.map(|number| std::char::from_digit(number, 10).unwrap())
|
||||
NUMBERS
|
||||
.into_iter()
|
||||
.map(|number| std::char::from_digit(number, 10).unwrap())
|
||||
}
|
||||
|
||||
//A hand consists of five cards labeled one of A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, or 2.
|
||||
//The relative strength of each card follows this order, where A is the highest and 2 is the lowest.
|
||||
pub static LABEL_ORDERING: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
|
||||
numbers_in_chars()
|
||||
.chain(LETTERS.iter().copied())
|
||||
.chain(LETTERS.into_iter().copied())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, character)| (character, index))
|
||||
.collect()
|
||||
|
@ -24,7 +27,8 @@ pub static LABEL_ORDERING: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
|
|||
pub static LABEL_ORDERING_WITH_JOKER: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
|
||||
std::iter::once(JOKER)
|
||||
.chain(numbers_in_chars())
|
||||
.chain(LETTERS_WITHOUT_JOKER.iter().copied())
|
||||
.chain(LETTERS_WITHOUT_JOKER.into_iter().copied())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, character)| (character, index))
|
||||
.collect()
|
||||
|
@ -43,7 +47,7 @@ pub fn compare_along_str(
|
|||
.get(&right_char)
|
||||
.unwrap_or_else(|| panic!("right char ({}) is not valid", right_char));
|
||||
|
||||
let current_ordering = left_rank_value.cmp(right_rank_value);
|
||||
let current_ordering = left_rank_value.cmp(&right_rank_value);
|
||||
|
||||
let found_difference = current_ordering != Ordering::Equal;
|
||||
if found_difference {
|
||||
|
|
|
@ -5,7 +5,7 @@ pub fn blocks_of_lines_seperated_by<'a>(
|
|||
let mut current_block = Vec::new();
|
||||
let mut lines = input.lines();
|
||||
std::iter::from_fn(move || {
|
||||
for next_line in lines.by_ref() {
|
||||
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);
|
||||
|
@ -23,7 +23,9 @@ pub fn blocks_of_lines_seperated_by<'a>(
|
|||
None
|
||||
})
|
||||
}
|
||||
pub fn blocks_of_lines_seperated_by_empty_lines(input: &str) -> impl Iterator<Item = Vec<&str>> {
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue