diff --git a/gleam.toml b/gleam.toml index ff28f07..df0dc71 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "gleam_community_maths" -version = "1.1.0" +version = "1.1.1" licences = ["Apache-2.0"] description = "A basic maths library" @@ -7,7 +7,7 @@ repository = { type = "github", user = "gleam-community", repo = "maths" } gleam = ">= 0.32.0" [dependencies] -gleam_stdlib = "~> 0.34" +gleam_stdlib = "~> 0.38" [dev-dependencies] gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml index 4a7c920..c3d0b0d 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,10 +2,10 @@ # You typically do not need to edit this file packages = [ - { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, + { name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" }, { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" }, ] [requirements] -gleam_stdlib = { version = "~> 0.34" } +gleam_stdlib = { version = "~> 0.38" } gleeunit = { version = "~> 1.0" } diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam index 7c691d3..e663d65 100644 --- a/src/gleam_community/maths/metrics.gleam +++ b/src/gleam_community/maths/metrics.gleam @@ -20,7 +20,7 @@ //// -//// +//// //// --- //// //// Metrics: A module offering functions for calculating distances and other @@ -46,15 +46,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_community/maths/arithmetics +import gleam_community/maths/conversion +import gleam_community/maths/elementary +import gleam_community/maths/piecewise import gleam/set import gleam/float import gleam/int @@ -258,7 +258,7 @@ pub fn norm( /// /// pub fn example() { /// let assert Ok(tol) = elementary.power(-10.0, -6.0) -/// +/// /// // Empty lists returns an error /// metrics.manhattan_distance([], [], option.None) /// |> should.be_error() @@ -329,7 +329,7 @@ pub fn manhattan_distance( /// // Differing lengths returns error /// metrics.minkowski_distance([], [1.0], 1.0, option.None) /// |> should.be_error() -/// +/// /// // Test order < 1 /// metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0, option.None) /// |> should.be_error() @@ -580,7 +580,7 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) { /// [1., 2., 3.] /// |> metrics.median() /// |> should.equal(Ok(2.)) -/// +/// /// [1., 2., 3., 4.] /// |> metrics.median() /// |> should.equal(Ok(2.5)) @@ -593,33 +593,31 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) { /// /// /// -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) } } @@ -650,12 +648,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) @@ -727,12 +725,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) @@ -759,7 +757,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 8939dc7..452e93d 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)) /// @@ -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 @@ -628,7 +628,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). /// ///
@@ -874,7 +874,7 @@ pub fn maximum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { ///
/// /// The minmax function takes two arguments $$x, y$$ along with a function -/// for comparing $$x, y$$. The function returns a tuple with the smallest +/// for comparing $$x, y$$. The function returns a tuple with the smallest /// value first and largest second. /// ///
@@ -909,7 +909,7 @@ pub fn minmax(x: a, y: a, compare: fn(a, a) -> order.Order) -> #(a, a) { /// /// /// -/// Returns the minimum value of a given list. +/// Returns the minimum value of a given list. /// ///
/// Example: @@ -923,7 +923,7 @@ pub fn minmax(x: a, y: a, compare: fn(a, a) -> order.Order) -> #(a, a) { /// [] /// |> piecewise.list_minimum(int.compare) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [4, 4, 3, 2, 1] /// |> piecewise.list_minimum(int.compare) @@ -944,17 +944,15 @@ pub fn list_minimum( [] -> "Invalid input argument: The list is empty." |> Error - _ -> { - let assert Ok(val0) = list.at(arr, 0) - arr - |> list.fold(val0, fn(acc: a, element: a) { - case compare(element, acc) { - order.Lt -> element - _ -> acc - } - }) - |> Ok - } + [x, ..rest] -> + Ok( + list.fold(rest, x, fn(acc: a, element: a) { + case compare(element, acc) { + order.Lt -> element + _ -> acc + } + }), + ) } } @@ -964,7 +962,7 @@ pub fn list_minimum( /// /// /// -/// Returns the maximum value of a given list. +/// Returns the maximum value of a given list. /// ///
/// Example: @@ -1000,17 +998,15 @@ pub fn list_maximum( [] -> "Invalid input argument: The list is empty." |> Error - _ -> { - let assert Ok(val0) = list.at(arr, 0) - arr - |> list.fold(val0, fn(acc: a, element: a) { - case compare(acc, element) { - order.Lt -> element - _ -> acc - } - }) - |> Ok - } + [x, ..rest] -> + Ok( + list.fold(rest, x, fn(acc: a, element: a) { + case compare(acc, element) { + order.Lt -> element + _ -> acc + } + }), + ) } } @@ -1026,7 +1022,7 @@ pub fn list_maximum( /// /// /// -/// Returns the indices of the minimum values in a given list. +/// Returns the indices of the minimum values in a given list. /// ///
/// Example: @@ -1040,7 +1036,7 @@ pub fn list_maximum( /// [] /// |> piecewise.arg_minimum(float.compare) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] /// |> piecewise.arg_minimum(float.compare) @@ -1110,7 +1106,7 @@ pub fn arg_minimum( /// [] /// |> piecewise.arg_maximum(float.compare) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] /// |> piecewise.arg_maximum(float.compare) @@ -1166,7 +1162,7 @@ pub fn arg_maximum( /// /// /// -/// Returns a tuple consisting of the minimum and maximum values of a given list. +/// Returns a tuple consisting of the minimum and maximum values of a given list. /// ///
/// Example: @@ -1180,7 +1176,7 @@ pub fn arg_maximum( /// [] /// |> piecewise.extrema(float.compare) /// |> should.be_error() -/// +/// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] /// |> piecewise.extrema(float.compare) @@ -1202,21 +1198,18 @@ pub fn extrema( [] -> "Invalid input argument: The list is empty." |> Error - _ -> { - let assert Ok(val_max) = list.at(arr, 0) - let assert Ok(val_min) = list.at(arr, 0) - arr - |> list.fold(#(val_min, val_max), fn(acc: #(a, a), element: a) { - let first: a = pair.first(acc) - let second: a = pair.second(acc) - case compare(element, first), compare(second, element) { - order.Lt, order.Lt -> #(element, element) - order.Lt, _ -> #(element, second) - _, order.Lt -> #(first, element) - _, _ -> #(first, second) - } - }) - |> Ok - } + [x, ..rest] -> + Ok( + list.fold(rest, #(x, x), fn(acc: #(a, a), element: a) { + let first: a = pair.first(acc) + let second: a = pair.second(acc) + case compare(element, first), compare(second, element) { + order.Lt, order.Lt -> #(element, element) + order.Lt, _ -> #(element, second) + _, order.Lt -> #(first, element) + _, _ -> #(first, second) + } + }), + ) } }