#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
// clippy::restriction,
// clippy::pedantic
)]
#![allow(
clippy::fallible_impl_from,
clippy::needless_doctest_main,
clippy::redundant_pub_crate,
clippy::suboptimal_flops
)]
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![deny(rustdoc::all)]
#[derive(Debug, Copy, Clone)]
pub enum FractionNumber {
F32(f32),
F64(f64),
}
impl From<f32> for FractionNumber {
fn from(val: f32) -> Self {
Self::F32(val)
}
}
impl From<f64> for FractionNumber {
fn from(val: f64) -> Self {
Self::F64(val)
}
}
impl FractionNumber {
fn format(self, precision: FormatPrecision) -> String {
match self {
Self::F32(val) => {
format!("{val:.precision$}", val = val, precision = precision.val())
}
Self::F64(val) => {
format!("{val:.precision$}", val = val, precision = precision.val())
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum FormatPrecision {
Exact(u8),
Max(u8),
}
impl FormatPrecision {
const fn val(self) -> usize {
let val = match self {
Self::Exact(val) => val,
Self::Max(val) => val,
};
val as usize
}
}
pub fn fmt_align_fractions(
fractions: &[FractionNumber],
precision: FormatPrecision,
) -> Vec<String> {
let fraction_strings = fractions
.iter()
.map(|fr| fr.format(precision))
.collect::<Vec<String>>();
let str_vec = fraction_strings
.iter()
.map(|s| s.as_str())
.collect::<Vec<&str>>();
fmt_align_fraction_strings(&str_vec)
}
pub fn fmt_align_fraction_strings(strings: &[&str]) -> Vec<String> {
let strings = strings
.iter()
.map(|x| normalize_fraction_part(x))
.collect::<Vec<&str>>();
let max = strings
.iter()
.map(|x| get_whole_part(x))
.map(|x| x.len())
.max()
.unwrap();
let mut new_strings = vec![String::new(); strings.len()];
strings.iter().enumerate().for_each(|(index, string)| {
let whole_part = get_whole_part(string);
let spaces = max - whole_part.len();
new_strings[index].push_str(&" ".repeat(spaces));
new_strings[index].push_str(string);
});
let max = new_strings.iter().map(|s| s.len()).max().unwrap();
for string in &mut new_strings {
let spaces = max - string.len();
string.push_str(&" ".repeat(spaces))
}
new_strings
}
fn get_whole_part(string: &str) -> &str {
string.split('.').next().unwrap()
}
fn get_fractional_part(string: &str) -> Option<&str> {
let mut split = string.split('.');
let _whole_part = split.next().unwrap();
split.next()
}
fn normalize_fraction_part(string: &str) -> &str {
let whole_part = get_whole_part(string);
let fraction_part = get_fractional_part(string);
if fraction_part.is_none() {
return whole_part;
}
let fraction_part = fraction_part.unwrap();
let zeroes = fraction_part_count_zeroes(fraction_part);
if fraction_part.len() == zeroes {
whole_part
} else {
&string[0..string.len() - zeroes]
}
}
fn fraction_part_count_zeroes(fraction_part: &str) -> usize {
let mut zeroes = 0;
let chars = fraction_part.chars().collect::<Vec<char>>();
for i in 0..fraction_part.len() {
let i = fraction_part.len() - 1 - i;
let char = chars[i];
if char == '0' {
zeroes += 1;
} else {
break;
}
}
zeroes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fraction_part_count_zeroes() {
assert_eq!(3, fraction_part_count_zeroes("123000"));
assert_eq!(0, fraction_part_count_zeroes("123"));
assert_eq!(1, fraction_part_count_zeroes("0"));
assert_eq!(11, fraction_part_count_zeroes("00000012800000000000"));
}
#[test]
fn test_fmt_align_fraction_strings() {
let res = fmt_align_fraction_strings(
&vec!["-42", "0.3214", "1000", "-1000.2"].into_boxed_slice(),
);
assert_eq!(" -42 ", res[0]);
assert_eq!(" 0.3214", res[1]);
assert_eq!(" 1000 ", res[2]);
assert_eq!("-1000.2 ", res[3]);
let res = fmt_align_fractions(
&vec![
FractionNumber::F64(-42.0),
FractionNumber::F64(0.3214),
FractionNumber::F64(1000.0),
FractionNumber::F64(-1000.2),
]
.into_boxed_slice(),
FormatPrecision::Max(4),
);
assert_eq!(" -42 ", res[0]);
assert_eq!(" 0.3214", res[1]);
assert_eq!(" 1000 ", res[2]);
assert_eq!("-1000.2 ", res[3]);
}
#[test]
fn test_fmt_unnecessary_zeroes_are_removed() {
let res = fmt_align_fraction_strings(&vec!["1.000000000"].into_boxed_slice());
assert_eq!("1", res[0]);
let res = fmt_align_fractions(
&vec![FractionNumber::F32(1.0), FractionNumber::F64(1.0)].into_boxed_slice(),
FormatPrecision::Max(4),
);
assert_eq!("1", res[0]);
assert_eq!("1", res[1]);
}
#[test]
fn test_fmt_nan() {
let res = fmt_align_fractions(
&[FractionNumber::F32(f32::NAN), FractionNumber::F64(f64::NAN)],
FormatPrecision::Max(20), );
assert_eq!("NaN", res[0]);
assert_eq!("NaN", res[1]);
}
}