diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam index e35e336..725691b 100644 --- a/src/gleam_community/maths/metrics.gleam +++ b/src/gleam_community/maths/metrics.gleam @@ -20,11 +20,11 @@ //// -//// +//// //// --- -//// +//// //// Metrics: A module offering functions for calculating distances and other types of metrics. -//// +//// //// * **Distances** //// * [`norm`](#norm) //// * [`manhatten_distance`](#float_manhatten_distance) @@ -35,16 +35,15 @@ //// * [`median`](#median) //// * [`variance`](#variance) //// * [`standard_deviation`](#standard_deviation) -//// +//// -import gleam_community/maths/elementary -import gleam_community/maths/piecewise -import gleam_community/maths/arithmetics -import gleam_community/maths/predicates -import gleam_community/maths/conversion +import gleam/bool import gleam/list import gleam/pair -import gleam/float +import gleam_community/maths/arithmetics +import gleam_community/maths/conversion +import gleam_community/maths/elementary +import gleam_community/maths/piecewise ///
/// -pub fn median(arr: List(Float)) -> Result(Float, String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let count: Int = list.length(arr) - let mid: Int = list.length(arr) / 2 - let sorted: List(Float) = list.sort(arr, float.compare) - case predicates.is_odd(count) { - // If there is an odd number of elements in the list, then the median - // is just the middle value - True -> { - let assert Ok(val0) = list.at(sorted, mid) - val0 - |> Ok - } - // If there is an even number of elements in the list, then the median - // is the mean of the two middle values - False -> { - let assert Ok(val0) = list.at(sorted, mid - 1) - let assert Ok(val1) = list.at(sorted, mid) - [val0, val1] - |> mean() - } - } - } +pub fn median(arr: List(Float)) -> Result(Float, Nil) { + use <- bool.guard(list.is_empty(arr), Error(Nil)) + let length = list.length(arr) + let mid = length / 2 + + case length % 2 == 0 { + True -> do_median(arr, mid, True, 0) + False -> do_median(arr, mid, False, 0) + } +} + +fn do_median( + xs: List(Float), + mid: Int, + mean: Bool, + index: Int, +) -> Result(Float, Nil) { + use <- bool.guard(index > mid, Error(Nil)) + let mid_less_one = mid - 1 + + case xs { + [x, ..] if !mean && index == mid -> Ok(x) + [x, y, ..] if mean && index == mid_less_one -> Ok({ x +. y } /. 2.0) + [_, ..rest] -> do_median(rest, mid, mean, index + 1) + [] -> Error(Nil) } } @@ -424,9 +421,9 @@ pub fn median(arr: List(Float)) -> Result(Float, String) { /// s^{2} = \frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x}) /// \\] /// -/// In the formula, $$n$$ is the sample size (the length of the list) and -/// $$x_i$$ is the sample point in the input list indexed by $$i$$. -/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta +/// In the formula, $$n$$ is the sample size (the length of the list) and +/// $$x_i$$ is the sample point in the input list indexed by $$i$$. +/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta /// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased /// estimate of the sample variance. Setting $$d = 1$$ gives an unbiased estimate. /// @@ -439,12 +436,12 @@ pub fn median(arr: List(Float)) -> Result(Float, String) { /// pub fn example () { /// // Degrees of freedom /// let ddof: Int = 1 -/// +/// /// // An empty list returns an error /// [] /// |> metrics.variance(ddof) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [1., 2., 3.] /// |> metrics.variance(ddof) @@ -500,9 +497,9 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { /// s = \left(\frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x})\right)^{\frac{1}{2}} /// \\] /// -/// In the formula, $$n$$ is the sample size (the length of the list) and -/// $$x_i$$ is the sample point in the input list indexed by $$i$$. -/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta +/// In the formula, $$n$$ is the sample size (the length of the list) and +/// $$x_i$$ is the sample point in the input list indexed by $$i$$. +/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta /// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased /// estimate of the sample standard deviation. Setting $$d = 1$$ gives an unbiased estimate. /// @@ -515,12 +512,12 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { /// pub fn example () { /// // Degrees of freedom /// let ddof: Int = 1 -/// +/// /// // An empty list returns an error /// [] /// |> metrics.standard_deviationddof) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [1., 2., 3.] /// |> metrics.standard_deviation(ddof) @@ -547,7 +544,7 @@ pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, String) False -> { let assert Ok(variance) = variance(arr, ddof) // The computed variance will always be positive - // So an error should never be returned + // So an error should never be returned let assert Ok(stdev) = elementary.square_root(variance) stdev |> Ok diff --git a/src/gleam_community/maths/piecewise.gleam b/src/gleam_community/maths/piecewise.gleam index c8492da..da48e0f 100644 --- a/src/gleam_community/maths/piecewise.gleam +++ b/src/gleam_community/maths/piecewise.gleam @@ -20,11 +20,11 @@ //// -//// +//// //// --- -//// +//// //// Piecewise: A module containing functions that have different definitions depending on conditions or intervals of their domain. -//// +//// //// * **Rounding functions** //// * [`ceiling`](#ceiling) //// * [`floor`](#floor) @@ -52,11 +52,11 @@ //// * [`arg_maximum`](#arg_maximum) //// -import gleam/option +import gleam/int import gleam/list +import gleam/option import gleam/order import gleam/pair -import gleam/int import gleam_community/maths/conversion import gleam_community/maths/elementary @@ -66,7 +66,7 @@ import gleam_community/maths/elementary /// /// /// -/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. +/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. /// /// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundUp`. /// @@ -325,7 +325,7 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// piecewise.round(12.0654, option.None, option.Some(piecewise.RoundNearest)) /// |> should.equal(Ok(12.0)) /// -/// // The default rounding mode is "RoundNearest" if None is provided +/// // The default rounding mode is "RoundNearest" if None is provided /// piecewise.round(12.0654, option.None, option.None) /// |> should.equal(Ok(12.0)) /// @@ -490,7 +490,7 @@ fn do_ceiling(a: Float) -> Float /// The absolute value: /// /// \\[ -/// \forall x, y \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}. +/// \forall x, y \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}. /// \\] /// /// The function takes an input $$x$$ and returns a positive float value. @@ -519,7 +519,7 @@ pub fn float_absolute_value(x: Float) -> Float { /// The absolute value: /// /// \\[ -/// \forall x, y \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}. +/// \forall x, y \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}. /// \\] /// /// The function takes an input $$x$$ and returns a positive integer value. @@ -548,7 +548,7 @@ pub fn int_absolute_value(x: Int) -> Int { /// The absolute difference: /// /// \\[ -/// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}. +/// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}. /// \\] /// /// The function takes two inputs $$x$$ and $$y$$ and returns a positive float @@ -589,7 +589,7 @@ pub fn float_absolute_difference(a: Float, b: Float) -> Float { /// The absolute difference: /// /// \\[ -/// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. +/// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. /// \\] /// /// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer value which is the the absolute difference of the inputs. @@ -627,7 +627,7 @@ pub fn int_absolute_difference(a: Int, b: Int) -> Int { /// /// /// The function takes an input $$x \in \mathbb{R}$$ and returns the sign of -/// the input, indicating whether it is positive (+1.0), negative (-1.0), or +/// the input, indicating whether it is positive (+1.0), negative (-1.0), or /// zero (0.0). /// ///