diff --git a/s4_prob/p01/Cargo.lock b/s4_prob/p01/Cargo.lock new file mode 100644 index 0000000..9c13181 --- /dev/null +++ b/s4_prob/p01/Cargo.lock @@ -0,0 +1,175 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p01" +version = "0.1.0" +dependencies = [ + "fern", + "humantime", + "log", + "num-traits", + "ordered-float", + "tabled", +] + +[[package]] +name = "papergrid" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b915f831b85d984193fdc3d3611505871dc139b2534530fa01c1a6a6707b6723" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tabled" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121d8171ee5687a4978d1b244f7d99c43e7385a272185a2f1e1fa4dc0979d444" +dependencies = [ + "papergrid", + "tabled_derive", +] + +[[package]] +name = "tabled_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d9946811baad81710ec921809e2af67ad77719418673b2a3794932d57b7538" +dependencies = [ + "heck", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" diff --git a/s4_prob/p01/Cargo.toml b/s4_prob/p01/Cargo.toml new file mode 100644 index 0000000..030f301 --- /dev/null +++ b/s4_prob/p01/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "p01" +version = "0.1.0" +edition = "2024" + +[dependencies] +fern = "0.7.1" +humantime = "2.1.0" +log = "0.4.26" +num-traits = "0.2.19" +ordered-float = "5.0.0" +tabled = "0.18.0" diff --git a/s4_prob/p01/src/main.rs b/s4_prob/p01/src/main.rs new file mode 100644 index 0000000..80ec31c --- /dev/null +++ b/s4_prob/p01/src/main.rs @@ -0,0 +1,170 @@ +fn setup_logger() -> Result<(), fern::InitError> { + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "[{} {} {}:{}] {}", + humantime::format_rfc3339_seconds(std::time::SystemTime::now()), + record.level(), + record.file().unwrap(), + record.line().unwrap(), + message + )) + }) + .level(log::LevelFilter::Debug) + .chain(std::io::stdout()) + .apply()?; + Ok(()) +} + +pub trait IntoOrd { + type Output: Ord; + fn into_ord(self) -> Self::Output; +} + +impl IntoOrd for i32 { + type Output = i32; + fn into_ord(self) -> Self::Output { + self + } +} + +impl IntoOrd for f32 { + type Output = ordered_float::OrderedFloat; + fn into_ord(self) -> Self::Output { + self.into() + } +} + +pub struct Variation(std::collections::BTreeMap); + +impl Variation { + pub fn get(&self, value: T) -> Option<&usize> { + self.0.get(&value.into_ord()) + } + + // TODO: https://en.wikipedia.org/wiki/Histogram#Number_of_bins_and_width + pub fn get_count(&self, total: usize) -> usize { + let count = total.isqrt(); + log::debug!("n = {count}"); + count + } + + pub fn get_total(&self) -> usize { + let total = self.0.values().sum(); + log::debug!("n = {total}"); + total + } +} + +impl Variation +where + T::Output: Copy, +{ + pub fn get_first_key(&self) -> Option { + self.0.first_key_value().map(|(key, _)| *key) + } + + pub fn get_last_key(&self) -> Option { + self.0.last_key_value().map(|(key, _)| *key) + } + + pub fn get_step(&self, count: usize) -> T::Output + where + T::Output: num_traits::cast::FromPrimitive + + std::fmt::Display + + std::ops::Div + + std::ops::Sub, + { + let first = self.get_first_key().unwrap(); + let last = self.get_last_key().unwrap(); + let step = (last - first) / num_traits::FromPrimitive::from_usize(count).unwrap(); + log::debug!("h = (x_k - x_1) / m = ({last} - {first}) / {count} = {step}"); + step + } +} + +impl From<&[T]> for Variation { + fn from(items: &[T]) -> Self { + Self(items.iter().fold(Default::default(), |mut map, &item| { + *map.entry(item.into_ord()).or_insert(0) += 1; + map + })) + } +} + +impl std::fmt::Display for Variation +where + T::Output: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut builder = tabled::builder::Builder::new(); + for (i, (key, value)) in self.0.iter().enumerate() { + builder.push_column([format!("x_{i}"), key.to_string(), value.to_string()]); + } + let mut table = builder.build(); + table.with(tabled::settings::Style::modern()); + write!(f, "{table}") + } +} + +fn main() { + setup_logger().unwrap(); + + #[rustfmt::skip] + const ITEMS: &[i32] = &[ + 75, 85, 84, 81, 84, 80, 82, 76, 75, 77, + 80, 82, 81, 84, 85, 77, 76, 84, 83, 87, + 78, 77, 88, 86, 87, 79, 80, 79, 78, 87, + 76, 81, 83, 85, 78, 76, 83, 81, 84, 88, + ]; + let series = Variation::from(ITEMS); + let total = series.get_total(); + let count = series.get_count(total); + let step = series.get_step(count); + println!("{series}"); +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! assert_delta { + ($x:expr, $y:expr, $d:expr) => { + if !($x - $y < $d || $y - $x < $d) { + panic!(); + } + }; + } + + #[test] + fn it_works() { + #[rustfmt::skip] + const ITEMS: &[f32] = &[ + 0.87, 0.94, 0.99, 0.90, 0.90, 0.87, 0.85, 0.87, + 0.90, 0.94, 0.87, 0.87, 0.82, 0.90, 0.94, 0.90, + 0.85, 0.85, 0.87, 0.94, 0.81, 0.82, 0.87, 0.97, + 0.90, 0.94, 0.85, 0.81, 0.87, 0.85, 0.90, 0.82, + 0.99, 0.90, 0.94, 0.82, 0.97, 0.81, 0.85, 0.87, + ]; + let series = Variation::from(ITEMS); + const EXPECTED: &[(f32, usize)] = &[ + (0.81, 3), + (0.82, 4), + (0.85, 6), + (0.87, 9), + (0.90, 8), + (0.94, 6), + (0.97, 2), + (0.99, 2), + ]; + for (value, count) in EXPECTED.iter() { + assert_eq!(*series.get(*value).unwrap(), *count); + } + let total = series.get_total(); + assert_eq!(total, 40); + let count = series.get_count(total); + assert_eq!(count, 6); + let step = series.get_step(count); + assert_delta!(step.0, 0.03, 1e-3); + } +}