From 4bd1081afdbdb750243a41e265cab2f7a5d2c18a Mon Sep 17 00:00:00 2001 From: wilhelmagren Date: Thu, 14 Dec 2023 12:13:44 +0100 Subject: [PATCH 1/4] [bench] criterion benchmarking format! vs pad --- benches/bench_main.rs | 7 +++++ .../benchmarks/format_whitespace_leftalign.rs | 29 +++++++++++++++++ benches/benchmarks/mod.rs | 2 ++ .../benchmarks/pad_whitespace_leftalign.rs | 31 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 benches/bench_main.rs create mode 100644 benches/benchmarks/format_whitespace_leftalign.rs create mode 100644 benches/benchmarks/mod.rs create mode 100644 benches/benchmarks/pad_whitespace_leftalign.rs diff --git a/benches/bench_main.rs b/benches/bench_main.rs new file mode 100644 index 0000000..ddf6759 --- /dev/null +++ b/benches/bench_main.rs @@ -0,0 +1,7 @@ +use criterion::criterion_main; +mod benchmarks; + +criterion_main! { + benchmarks::format_whitespace_leftalign::formats, + benchmarks::pad_whitespace_leftalign::pads, +} diff --git a/benches/benchmarks/format_whitespace_leftalign.rs b/benches/benchmarks/format_whitespace_leftalign.rs new file mode 100644 index 0000000..e93205f --- /dev/null +++ b/benches/benchmarks/format_whitespace_leftalign.rs @@ -0,0 +1,29 @@ +use criterion::{black_box, criterion_group, Criterion}; + +pub fn format_whitespace_10_leftalign(c: &mut Criterion) { + let width: usize = 10; + c.bench_function("format! ws 10 la", |b| b.iter(|| black_box(format!("{: Date: Thu, 14 Dec 2023 12:14:01 +0100 Subject: [PATCH 2/4] [feat] implement efficient padding --- src/lib.rs | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8f29479 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,228 @@ +/* +* MIT License +* +* Copyright (c) 2023 Firelink Data +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* File created: 2023-12-14 +* Last updated: 2023-12-14 +*/ + +use std::fmt; + +/// +#[derive(Debug, Clone)] +pub struct WidthError; + +/// +impl fmt::Display for WidthError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Invalid target pad width for the provided string length.") + } +} + +/// +pub enum Alignment { + Left, + Right, + Center, +} + +/// +pub fn whitespace( + string: &str, + width: usize, + mode: Alignment, +) -> String { + pad(string, width, mode, ' ') +} + +/// +pub fn zeros( + string: &str, + width: usize, + mode: Alignment, +) -> String { + pad(string, width, mode, '0') +} + +/// +pub fn pad_into_bytes( + string: &str, + width: usize, + mode: Alignment, + pad_char: char, +) -> Vec { + pad(string, width, mode, pad_char).into_bytes() +} + +/// +pub fn pad_and_push_to_buffer( + string: &str, + width: usize, + mode: Alignment, + pad_char: char, + buffer: &mut Vec, +) { + buffer.extend_from_slice( + pad(string, width, mode, pad_char).as_bytes() + ); +} + +/// +/// Panics +/// Iff the target pad width is less than the provided string length. +fn pad( + string: &str, + width: usize, + mode: Alignment, + pad_char: char, +) -> String { + + if width < string.len() { panic!("Invalid target pad width for the provide string length.") } + + let mut output = String::with_capacity(width); + let diff: usize = width - string.len(); + + if diff == 0 { return string.to_string(); } + + let (lpad, rpad) = match mode { + Alignment::Left => (0, diff), + Alignment::Right => (diff, 0), + Alignment::Center => (diff / 2, diff - diff / 2), + }; + + (0..lpad).for_each(|_| output.push(pad_char)); + output.push_str(string); + (0..rpad).for_each(|_| output.push(pad_char)); + + output +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn pad_should_panic() { + let _ = whitespace("lol hahahah xd test", 15, Alignment::Center); + } + + #[test] + fn pad_whitespace_right_align_into_bytes() { + let width: usize = 30; + let bytes = pad_into_bytes("this is cool", width, Alignment::Right, ' '); + + assert_eq!( + format!("{:>width$}", "this is cool").into_bytes(), + bytes, + ); + } + + #[test] + fn pad_zeros_left_align_push_to_buffer() { + let width: usize = 128; + let expected: Vec = format!("{:0 = Vec::with_capacity(1024 * 1024); + pad_and_push_to_buffer("testing buffer reuse smart memory", width, Alignment::Left, '0', &mut buffer); + + assert_eq!(expected.len(), buffer.len()); + assert_eq!(expected, buffer); + } + + #[test] + fn pad_whitespace_left_align() { + let width: usize = 30; + let pad = whitespace("this is cool", width, Alignment::Left); + + assert_eq!( + format!("{:width$}", "this is cool"), + pad + ); + } + + #[test] + fn pad_whitespace_center_align() { + let width: usize = 30; + let pad = whitespace("this is cool", width, Alignment::Center); + + assert_eq!( + format!("{:^width$}", "this is cool"), + pad + ); + } + + #[test] + fn pad_zeros_left_align() { + let width: usize = 30; + let pad = zeros("this is cool", width, Alignment::Left); + + assert_eq!( + format!("{:0width$}", "this is cool"), + pad + ); + } + + #[test] + fn pad_zeros_center_align() { + let width: usize = 30; + let pad = zeros("this is cool", width, Alignment::Center); + + assert_eq!( + format!("{:0^width$}", "this is cool"), + pad + ); + } + + #[test] + fn pad_1000000_zeros_center() { + let width: usize = 100_000_000; + let pad = zeros("this is cool", width, Alignment::Center); + + assert_eq!( + format!("{:0^width$}", "this is cool"), + pad + ); + } +} From cee9b0f1fbbda489dc4a9858ac4ff47f98ae9556 Mon Sep 17 00:00:00 2001 From: wilhelmagren Date: Thu, 14 Dec 2023 12:14:24 +0100 Subject: [PATCH 3/4] [build] crate metadata, deps, and criterion bench conf --- Cargo.toml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..75061aa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "padder" +version = "0.1.0" +edition = "2021" +description = "String formatting done right." +authors = [ + "Wilhelm Ågren ", +] + +readme = "README.md" +license-file = "LICENSE" +homepage = "https://github.com/firelink-data/padder" +repository = "https://github.com/firelink-data/padder" + +include = [ + "**/*.rs", + "Cargo.toml", + "LICENSE", + "README.md", +] + +[lib] +name = "padder" +path = "src/lib.rs" +test = true +bench = true +crate-type = [ "lib" ] + +[dependencies] + +[dev-dependencies] +criterion = "0.5.1" + +[[bench]] +name = "bench_main" +harness = false From 82d02f2ef45478f5ecf57edbbd5e6af2b5a47d2a Mon Sep 17 00:00:00 2001 From: wilhelmagren Date: Thu, 14 Dec 2023 12:29:28 +0100 Subject: [PATCH 4/4] [feat] cargo fmt and clippy lint changes --- .../benchmarks/format_whitespace_leftalign.rs | 21 +++- .../benchmarks/pad_whitespace_leftalign.rs | 30 +++++- src/lib.rs | 95 +++++++------------ 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/benches/benchmarks/format_whitespace_leftalign.rs b/benches/benchmarks/format_whitespace_leftalign.rs index e93205f..13170ff 100644 --- a/benches/benchmarks/format_whitespace_leftalign.rs +++ b/benches/benchmarks/format_whitespace_leftalign.rs @@ -2,22 +2,35 @@ use criterion::{black_box, criterion_group, Criterion}; pub fn format_whitespace_10_leftalign(c: &mut Criterion) { let width: usize = 10; - c.bench_function("format! ws 10 la", |b| b.iter(|| black_box(format!("{: fmt::Result { - write!(f, "Invalid target pad width for the provided string length.") + write!( + f, + "Invalid target pad width for the provided string length." + ) } } @@ -46,30 +49,17 @@ pub enum Alignment { } /// -pub fn whitespace( - string: &str, - width: usize, - mode: Alignment, -) -> String { +pub fn whitespace(string: &str, width: usize, mode: Alignment) -> String { pad(string, width, mode, ' ') } /// -pub fn zeros( - string: &str, - width: usize, - mode: Alignment, -) -> String { +pub fn zeros(string: &str, width: usize, mode: Alignment) -> String { pad(string, width, mode, '0') } /// -pub fn pad_into_bytes( - string: &str, - width: usize, - mode: Alignment, - pad_char: char, -) -> Vec { +pub fn pad_into_bytes(string: &str, width: usize, mode: Alignment, pad_char: char) -> Vec { pad(string, width, mode, pad_char).into_bytes() } @@ -81,27 +71,23 @@ pub fn pad_and_push_to_buffer( pad_char: char, buffer: &mut Vec, ) { - buffer.extend_from_slice( - pad(string, width, mode, pad_char).as_bytes() - ); + buffer.extend_from_slice(pad(string, width, mode, pad_char).as_bytes()); } /// /// Panics /// Iff the target pad width is less than the provided string length. -fn pad( - string: &str, - width: usize, - mode: Alignment, - pad_char: char, -) -> String { - - if width < string.len() { panic!("Invalid target pad width for the provide string length.") } +fn pad(string: &str, width: usize, mode: Alignment, pad_char: char) -> String { + if width < string.len() { + panic!("Invalid target pad width for the provide string length.") + } let mut output = String::with_capacity(width); let diff: usize = width - string.len(); - if diff == 0 { return string.to_string(); } + if diff == 0 { + return string.to_string(); + } let (lpad, rpad) = match mode { Alignment::Left => (0, diff), @@ -131,19 +117,23 @@ mod tests { let width: usize = 30; let bytes = pad_into_bytes("this is cool", width, Alignment::Right, ' '); - assert_eq!( - format!("{:>width$}", "this is cool").into_bytes(), - bytes, - ); + assert_eq!(format!("{:>width$}", "this is cool").into_bytes(), bytes,); } #[test] fn pad_zeros_left_align_push_to_buffer() { let width: usize = 128; - let expected: Vec = format!("{:0 = + format!("{:0 = Vec::with_capacity(1024 * 1024); - pad_and_push_to_buffer("testing buffer reuse smart memory", width, Alignment::Left, '0', &mut buffer); + pad_and_push_to_buffer( + "testing buffer reuse smart memory", + width, + Alignment::Left, + '0', + &mut buffer, + ); assert_eq!(expected.len(), buffer.len()); assert_eq!(expected, buffer); @@ -154,10 +144,7 @@ mod tests { let width: usize = 30; let pad = whitespace("this is cool", width, Alignment::Left); - assert_eq!( - format!("{:width$}", "this is cool"), - pad - ); + assert_eq!(format!("{:>width$}", "this is cool"), pad); } #[test] @@ -176,10 +160,7 @@ mod tests { let width: usize = 30; let pad = whitespace("this is cool", width, Alignment::Center); - assert_eq!( - format!("{:^width$}", "this is cool"), - pad - ); + assert_eq!(format!("{:^width$}", "this is cool"), pad); } #[test] @@ -187,10 +168,7 @@ mod tests { let width: usize = 30; let pad = zeros("this is cool", width, Alignment::Left); - assert_eq!( - format!("{:0width$}", "this is cool"), - pad - ); + assert_eq!(format!("{:0>width$}", "this is cool"), pad); } #[test] @@ -209,10 +184,7 @@ mod tests { let width: usize = 30; let pad = zeros("this is cool", width, Alignment::Center); - assert_eq!( - format!("{:0^width$}", "this is cool"), - pad - ); + assert_eq!(format!("{:0^width$}", "this is cool"), pad); } #[test] @@ -220,9 +192,6 @@ mod tests { let width: usize = 100_000_000; let pad = zeros("this is cool", width, Alignment::Center); - assert_eq!( - format!("{:0^width$}", "this is cool"), - pad - ); + assert_eq!(format!("{:0^width$}", "this is cool"), pad); } }