From d6d5011584ddbcd3ddc2abbe533ff0c05d65e1ac Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:32:56 +0000 Subject: [PATCH 1/6] Added a geometric mean function attempting to follow the same style as the previous averages. This is a core average function used in finance amongst other areas. I've added some tests to go with it. --- src/math/average.rs | 94 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/src/math/average.rs b/src/math/average.rs index f420b2268de..caa34da58d7 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -6,18 +6,32 @@ The mode is the most frequently occurring value on the list. Reference: https://www.britannica.com/science/mean-median-and-mode -This program approximates the mean, median and mode of a finite sequence. +There is also the geometric mean, often used in finance, which is much more suited for rates and other multiplicative +relationships. The geometric mean is the Nth root of the product of a finite sequence of numbers. + +This program approximates the mean, geometric mean, median and mode of a finite sequence. Note: Floats sequences are not allowed for `mode` function. "] use std::collections::HashMap; use std::collections::HashSet; -use num_traits::Num; +use num_traits::{Num, FromPrimitive, ToPrimitive, One}; fn sum(sequence: Vec) -> T { sequence.iter().fold(T::zero(), |acc, x| acc + *x) } +fn product(sequence: &Vec) -> Option { + if sequence.is_empty() { + None + } else { + sequence.iter() + .copied() + .fold(T::one(), |acc, x| acc * x) + .to_f64() + } +} + /// # Argument /// /// * `sequence` - A vector of numbers. @@ -34,6 +48,22 @@ fn mean_of_two(a: T, b: T) -> T { (a + b) / (T::one() + T::one()) } + +/// # Argument +/// +/// * `sequence` - A vector of numbers. +/// Returns geometric mean of `sequence`. +pub fn geometric_mean(sequence: &Vec) -> Option { + if sequence.is_empty() { + return None; + } + if sequence.iter().any(|&x| x.to_f64() <= Some(0.0)) { + return None; + } + let product_result = product(sequence)?; + Some(product_result.powf(1.0 / sequence.len() as f64)) +} + /// # Argument /// /// * `sequence` - A vector of numbers. @@ -119,4 +149,62 @@ mod test { assert!(mean(Vec::::new()).is_none()); assert!(mean(Vec::::new()).is_none()); } -} + + // Tests for product function + // Empty Product is empty + #[test] + fn test_product_empty() { + let sequence: Vec = vec![]; + let result = product(&sequence); + assert_eq!(result, None); + } + + // Product of a single value is the value + #[test] + fn test_product_single_element() { + let sequence = vec![10]; + let result = product(&sequence); + assert_eq!(result, Some(10.0)); + } + // Product generic test + #[test] + fn test_product_floats() { + let sequence = vec![1.5, 2.0, 4.0]; + let result = product(&sequence); + assert_eq!(result, Some(12.0)); + } + + // Tests for geometric mean function + // Empty sequence returns nothing + #[test] + fn test_geometric_mean_empty() { + let sequence: Vec = vec![]; + let result = geometric_mean(&sequence); + assert_eq!(result, None); + } + + // Geometric mean of a single value is the value itself. + #[test] + fn test_geometric_mean_single_element() { + let sequence = vec![5.0]; + let result = geometric_mean(&sequence); + assert_eq!(result, Some(5.0)); + } + + // Geometric means are not defined for negative values + #[test] + fn test_geometric_mean_negative() { + let sequence = vec![1.0, -3.0, 2.0]; + let result = geometric_mean(&sequence); + assert_eq!(result, None); + } + + // Geometric mean generic test + #[test] + fn test_geometric_mean_floats() { + let sequence = vec![0.5, 0.5, 0.3, 0.2]; + let result = geometric_mean(&sequence); + assert_eq!(result, Some(0.34996355115805833)); + } + +} \ No newline at end of file From 28b198de8ecfce2da77bf7ca045d290136c34f35 Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Sun, 8 Dec 2024 15:48:04 +0000 Subject: [PATCH 2/6] Fixed clippy warnings and formatting --- src/math/average.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/math/average.rs b/src/math/average.rs index caa34da58d7..501c9afae2a 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -15,17 +15,19 @@ Note: Floats sequences are not allowed for `mode` function. use std::collections::HashMap; use std::collections::HashSet; -use num_traits::{Num, FromPrimitive, ToPrimitive, One}; +use num_traits::{FromPrimitive, Num, One, ToPrimitive}; fn sum(sequence: Vec) -> T { sequence.iter().fold(T::zero(), |acc, x| acc + *x) } -fn product(sequence: &Vec) -> Option { +#[allow(dead_code)] +fn product(sequence: &[T]) -> Option { if sequence.is_empty() { None } else { - sequence.iter() + sequence + .iter() .copied() .fold(T::one(), |acc, x| acc * x) .to_f64() @@ -48,12 +50,14 @@ fn mean_of_two(a: T, b: T) -> T { (a + b) / (T::one() + T::one()) } - /// # Argument /// /// * `sequence` - A vector of numbers. /// Returns geometric mean of `sequence`. -pub fn geometric_mean(sequence: &Vec) -> Option { +#[allow(dead_code)] +pub fn geometric_mean( + sequence: &[T], +) -> Option { if sequence.is_empty() { return None; } @@ -156,7 +160,7 @@ mod test { fn test_product_empty() { let sequence: Vec = vec![]; let result = product(&sequence); - assert_eq!(result, None); + assert_eq!(result, None); } // Product of a single value is the value @@ -185,7 +189,7 @@ mod test { // Geometric mean of a single value is the value itself. #[test] - fn test_geometric_mean_single_element() { + fn test_geometric_mean_single_element() { let sequence = vec![5.0]; let result = geometric_mean(&sequence); assert_eq!(result, Some(5.0)); @@ -206,5 +210,4 @@ mod test { let result = geometric_mean(&sequence); assert_eq!(result, Some(0.34996355115805833)); } - -} \ No newline at end of file +} From 719a357fc0b9325726ec40c82edd1576d48aa969 Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:19:15 +0000 Subject: [PATCH 3/6] Update src/math/average.rs Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com> --- src/math/average.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/math/average.rs b/src/math/average.rs index 501c9afae2a..a52b116fc64 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -6,8 +6,6 @@ The mode is the most frequently occurring value on the list. Reference: https://www.britannica.com/science/mean-median-and-mode -There is also the geometric mean, often used in finance, which is much more suited for rates and other multiplicative -relationships. The geometric mean is the Nth root of the product of a finite sequence of numbers. This program approximates the mean, geometric mean, median and mode of a finite sequence. Note: Floats sequences are not allowed for `mode` function. From e29ab7f08f1c41ef5062d9fca98c09d1b8174831 Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:19:21 +0000 Subject: [PATCH 4/6] Update src/math/average.rs Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com> --- src/math/average.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/math/average.rs b/src/math/average.rs index a52b116fc64..6ccf9dd3e20 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -19,7 +19,6 @@ fn sum(sequence: Vec) -> T { sequence.iter().fold(T::zero(), |acc, x| acc + *x) } -#[allow(dead_code)] fn product(sequence: &[T]) -> Option { if sequence.is_empty() { None From fb46fed28eead76a8966983c2e99f2e752870669 Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:19:27 +0000 Subject: [PATCH 5/6] Update src/math/average.rs Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com> --- src/math/average.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/math/average.rs b/src/math/average.rs index 6ccf9dd3e20..2c1be21576e 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -51,7 +51,6 @@ fn mean_of_two(a: T, b: T) -> T { /// /// * `sequence` - A vector of numbers. /// Returns geometric mean of `sequence`. -#[allow(dead_code)] pub fn geometric_mean( sequence: &[T], ) -> Option { From 1e563984c8799187215db556995a384769ea9eed Mon Sep 17 00:00:00 2001 From: Marmaa <104031529+Marmaa0@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:19:42 +0000 Subject: [PATCH 6/6] Update src/math/average.rs Co-authored-by: Piotr Idzik <65706193+vil02@users.noreply.github.com> --- src/math/average.rs | 48 ++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/math/average.rs b/src/math/average.rs index 2c1be21576e..b7ba4eaa598 100644 --- a/src/math/average.rs +++ b/src/math/average.rs @@ -174,36 +174,22 @@ mod test { assert_eq!(result, Some(12.0)); } - // Tests for geometric mean function - // Empty sequence returns nothing - #[test] - fn test_geometric_mean_empty() { - let sequence: Vec = vec![]; - let result = geometric_mean(&sequence); - assert_eq!(result, None); - } - - // Geometric mean of a single value is the value itself. - #[test] - fn test_geometric_mean_single_element() { - let sequence = vec![5.0]; - let result = geometric_mean(&sequence); - assert_eq!(result, Some(5.0)); - } - - // Geometric means are not defined for negative values - #[test] - fn test_geometric_mean_negative() { - let sequence = vec![1.0, -3.0, 2.0]; - let result = geometric_mean(&sequence); - assert_eq!(result, None); - } - - // Geometric mean generic test - #[test] - fn test_geometric_mean_floats() { - let sequence = vec![0.5, 0.5, 0.3, 0.2]; - let result = geometric_mean(&sequence); - assert_eq!(result, Some(0.34996355115805833)); + macro_rules! test_geometric_mean { + ($($name:ident: $inputs:expr,)*) => { + $( + #[test] + fn $name() { + let (sequence, expected) = $inputs; + assert_eq!(geometric_mean(&sequence), expected); + } + )* + } + } + + test_geometric_mean! { + empty: (Vec::::new(), None), + single: (vec![5.0], Some(5.0)), + negative: (vec![1.0, -3.0, 2.0], None), + regular: (vec![0.5, 0.5, 0.3, 0.2], Some(0.34996355115805833)), } }