diff --git a/src/gleam_community/maths/conversion.gleam b/src/gleam_community/maths/conversion.gleam index d0eb040..9ac9036 100644 --- a/src/gleam_community/maths/conversion.gleam +++ b/src/gleam_community/maths/conversion.gleam @@ -30,6 +30,7 @@ //// * [`int_to_float`](#int_to_float) //// * [`degrees_to_radians`](#degrees_to_radians) //// * [`radians_to_degrees`](#radians_to_degrees) +//// import gleam/int diff --git a/src/gleam_community/maths/elementary.gleam b/src/gleam_community/maths/elementary.gleam index c71ebf6..e130e07 100644 --- a/src/gleam_community/maths/elementary.gleam +++ b/src/gleam_community/maths/elementary.gleam @@ -1000,7 +1000,7 @@ pub fn power(x: Float, y: Float) -> Result(Float, String) { // 2. If the base (x) is 0 and the exponent (y) is negative then the // expression is equivalent to the exponent (y) divided by 0 and an // error should be returned - case x <. 0.0 && fractional || x == 0.0 && y <. 0.0 { + case { x <. 0.0 && fractional } || { x == 0.0 && y <. 0.0 } { True -> "Invalid input argument: x < 0 and y is fractional or x = 0 and y < 0." |> Error diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam index e663d65..286044c 100644 --- a/src/gleam_community/maths/metrics.gleam +++ b/src/gleam_community/maths/metrics.gleam @@ -20,12 +20,18 @@ //// -//// +//// //// --- //// //// Metrics: A module offering functions for calculating distances and other //// types of metrics. //// +//// Disclaimer: In this module, the terms "distance" and "metric" are used in +//// a broad and practical sense. That is, they are used to denote any difference +//// or discrepancy between two inputs. Consequently, they may not align with their +//// precise mathematical definitions (in particular, some "distance" functions in +//// this module do not satisfy the triangle inequality). +//// //// * **Distance measures** //// * [`norm`](#norm) //// * [`manhattan_distance`](#manhattan_distance) @@ -40,26 +46,24 @@ //// * [`sorensen_dice_coefficient`](#sorensen_dice_coefficient) //// * [`tversky_index`](#tversky_index) //// * [`overlap_coefficient`](#overlap_coefficient) -//// * [`levenshtein_distance`](#levenshtein_distance) //// * **Basic statistical measures** //// * [`mean`](#mean) //// * [`median`](#median) //// * [`variance`](#variance) //// * [`standard_deviation`](#standard_deviation) -//// +//// import gleam/bool +import gleam/float +import gleam/int import gleam/list +import gleam/option import gleam/pair +import gleam/set 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 -import gleam/string -import gleam/option /// Utility function that checks all lists have the expected length and contents /// The function is primarily used by all distance measures taking 'List(Float)' @@ -1126,125 +1130,6 @@ pub fn cosine_similarity( } } -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculate the Levenshtein distance between two strings, i.e., measure the -/// difference between two strings (essentially sequences). It is defined as -/// the minimum number of single-character edits required to change one string -/// into the other, using operations: -/// - insertions -/// - deletions -/// - substitutions -/// -/// Note: The implementation is primarily based on the Elixir implementation -/// [levenshtein](https://hex.pm/packages/levenshtein). -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/metrics -/// -/// pub fn example () { -/// metrics.levenshtein_distance("hello", "hello") -/// |> should.equal(0) -/// -/// metrics.levenshtein_distance("cat", "cut") -/// |> should.equal(1) -/// -/// metrics.levenshtein_distance("kitten", "sitting") -/// |> should.equal(3) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -/// -pub fn levenshtein_distance(xstring: String, ystring: String) -> Int { - case xstring, ystring { - xstring, ystring if xstring == ystring -> { - 0 - } - xstring, ystring if xstring == "" -> { - string.length(ystring) - } - xstring, ystring if ystring == "" -> { - string.length(xstring) - } - _, _ -> { - let xstring_graphemes = string.to_graphemes(xstring) - let ystring_graphemes = string.to_graphemes(ystring) - let ystring_length = list.length(ystring_graphemes) - let distance_list = list.range(0, ystring_length) - - do_edit_distance(xstring_graphemes, ystring_graphemes, distance_list, 1) - } - } -} - -fn do_edit_distance( - xstring: List(String), - ystring: List(String), - distance_list: List(Int), - step: Int, -) -> Int { - case xstring { - // Safe as 'distance_list' is never empty - [] -> { - let assert Ok(last) = list.last(distance_list) - last - } - [xstring_head, ..xstring_tail] -> { - let new_distance_list = - distance_list_helper(ystring, distance_list, xstring_head, [step], step) - do_edit_distance(xstring_tail, ystring, new_distance_list, step + 1) - } - } -} - -fn distance_list_helper( - ystring: List(String), - distance_list: List(Int), - grapheme: String, - new_distance_list: List(Int), - last_distance: Int, -) -> List(Int) { - case ystring { - [] -> list.reverse(new_distance_list) - [ystring_head, ..ystring_tail] -> { - let assert [distance_list_head, ..distance_list_tail] = distance_list - let difference = case ystring_head == grapheme { - True -> { - 0 - } - False -> { - 1 - } - } - let assert [first, ..] = distance_list_tail - let min = - last_distance + 1 - |> piecewise.minimum(first + 1, int.compare) - |> piecewise.minimum(distance_list_head + difference, int.compare) - distance_list_helper( - ystring_tail, - distance_list_tail, - grapheme, - [min, ..new_distance_list], - min, - ) - } - } -} - ///
/// /// Spot a typo? Open an issue! @@ -1293,7 +1178,6 @@ fn distance_list_helper( /// ///
/// -/// pub fn canberra_distance( xarr: List(Float), yarr: List(Float), diff --git a/src/gleam_community/maths/predicates.gleam b/src/gleam_community/maths/predicates.gleam index e13f41d..e85e581 100644 --- a/src/gleam_community/maths/predicates.gleam +++ b/src/gleam_community/maths/predicates.gleam @@ -35,14 +35,15 @@ //// * [`is_even`](#is_even) //// * [`is_odd`](#is_odd) //// * [`is_prime`](#is_prime) +//// -import gleam/pair import gleam/int import gleam/list import gleam/option +import gleam/pair +import gleam_community/maths/arithmetics import gleam_community/maths/elementary import gleam_community/maths/piecewise -import gleam_community/maths/arithmetics ///
/// diff --git a/src/gleam_community/maths/sequences.gleam b/src/gleam_community/maths/sequences.gleam index 0ec8118..3512152 100644 --- a/src/gleam_community/maths/sequences.gleam +++ b/src/gleam_community/maths/sequences.gleam @@ -31,11 +31,12 @@ //// * [`linear_space`](#linear_space) //// * [`logarithmic_space`](#logarithmic_space) //// * [`geometric_space`](#geometric_space) +//// +import gleam/list +import gleam_community/maths/conversion import gleam_community/maths/elementary import gleam_community/maths/piecewise -import gleam_community/maths/conversion -import gleam/list ///
/// diff --git a/src/gleam_community/maths/special.gleam b/src/gleam_community/maths/special.gleam index 6b86e5a..2b79bae 100644 --- a/src/gleam_community/maths/special.gleam +++ b/src/gleam_community/maths/special.gleam @@ -32,10 +32,10 @@ //// * [`incomplete_gamma`](#incomplete_gamma) //// +import gleam/list import gleam_community/maths/conversion import gleam_community/maths/elementary import gleam_community/maths/piecewise -import gleam/list ///
/// diff --git a/test/gleam_community/maths/arithmetics_test.gleam b/test/gleam_community/maths/arithmetics_test.gleam index 990fce6..3643561 100644 --- a/test/gleam_community/maths/arithmetics_test.gleam +++ b/test/gleam_community/maths/arithmetics_test.gleam @@ -1,6 +1,6 @@ +import gleam/option import gleam_community/maths/arithmetics import gleeunit/should -import gleam/option pub fn int_gcd_test() { arithmetics.gcd(1, 1) diff --git a/test/gleam_community/maths/combinatorics_test.gleam b/test/gleam_community/maths/combinatorics_test.gleam index d1b3405..0b82534 100644 --- a/test/gleam_community/maths/combinatorics_test.gleam +++ b/test/gleam_community/maths/combinatorics_test.gleam @@ -1,6 +1,6 @@ -import gleam_community/maths/combinatorics -import gleam/set import gleam/list +import gleam/set +import gleam_community/maths/combinatorics import gleeunit/should pub fn int_factorial_test() { diff --git a/test/gleam_community/maths/conversion_test.gleam b/test/gleam_community/maths/conversion_test.gleam index 0a2e793..0a55a10 100644 --- a/test/gleam_community/maths/conversion_test.gleam +++ b/test/gleam_community/maths/conversion_test.gleam @@ -1,10 +1,10 @@ +import gleam_community/maths/conversion import gleam_community/maths/elementary import gleam_community/maths/predicates -import gleam_community/maths/conversion import gleeunit/should pub fn float_to_degree_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) conversion.radians_to_degrees(0.0) |> predicates.is_close(0.0, 0.0, tol) |> should.be_true() @@ -15,7 +15,7 @@ pub fn float_to_degree_test() { } pub fn float_to_radian_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) conversion.degrees_to_radians(0.0) |> predicates.is_close(0.0, 0.0, tol) |> should.be_true() diff --git a/test/gleam_community/maths/elementary_test.gleam b/test/gleam_community/maths/elementary_test.gleam index 71c62e8..a547f87 100644 --- a/test/gleam_community/maths/elementary_test.gleam +++ b/test/gleam_community/maths/elementary_test.gleam @@ -1,10 +1,10 @@ +import gleam/option import gleam_community/maths/elementary import gleam_community/maths/predicates import gleeunit/should -import gleam/option pub fn float_acos_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values let assert Ok(result) = elementary.acos(1.0) @@ -27,7 +27,7 @@ pub fn float_acos_test() { } pub fn float_acosh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values let assert Ok(result) = elementary.acosh(1.0) @@ -47,7 +47,7 @@ pub fn float_asin_test() { elementary.asin(0.0) |> should.equal(Ok(0.0)) - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) let assert Ok(result) = elementary.asin(0.5) result |> predicates.is_close(0.523598, 0.0, tol) @@ -63,7 +63,7 @@ pub fn float_asin_test() { } pub fn float_asinh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.asinh(0.0) @@ -76,7 +76,7 @@ pub fn float_asinh_test() { } pub fn float_atan_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.atan(0.0) @@ -89,7 +89,7 @@ pub fn float_atan_test() { } pub fn math_atan2_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.atan2(0.0, 0.0) @@ -137,7 +137,7 @@ pub fn math_atan2_test() { } pub fn float_atanh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values let assert Ok(result) = elementary.atanh(0.0) @@ -166,7 +166,7 @@ pub fn float_atanh_test() { } pub fn float_cos_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.cos(0.0) @@ -183,7 +183,7 @@ pub fn float_cos_test() { } pub fn float_cosh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.cosh(0.0) @@ -200,7 +200,7 @@ pub fn float_cosh_test() { } pub fn float_sin_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.sin(0.0) @@ -217,7 +217,7 @@ pub fn float_sin_test() { } pub fn float_sinh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.sinh(0.0) @@ -234,7 +234,7 @@ pub fn float_sinh_test() { } pub fn math_tan_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.tan(0.0) @@ -247,7 +247,7 @@ pub fn math_tan_test() { } pub fn math_tanh_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.tanh(0.0) @@ -268,7 +268,7 @@ pub fn math_tanh_test() { } pub fn float_exponential_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.exponential(0.0) @@ -285,7 +285,7 @@ pub fn float_exponential_test() { } pub fn float_natural_logarithm_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.natural_logarithm(1.0) @@ -336,7 +336,7 @@ pub fn float_logarithm_test() { } pub fn float_logarithm_2_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values elementary.logarithm_2(1.0) @@ -357,7 +357,7 @@ pub fn float_logarithm_2_test() { } pub fn float_logarithm_10_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values let assert Ok(result) = elementary.logarithm_10(1.0) @@ -400,6 +400,15 @@ pub fn float_power_test() { elementary.power(2.0, -1.0) |> should.equal(Ok(0.5)) + // When y = 0, the result should universally be 1, regardless of the value of x + elementary.power(10.0, 0.0) + |> should.equal(Ok(1.0)) + elementary.power(-10.0, 0.0) + |> should.equal(Ok(1.0)) + + elementary.power(2.0, -1.0) + |> should.equal(Ok(0.5)) + // elementary.power(-1.0, 0.5) is equivalent to float.square_root(-1.0) // and should return an error as an imaginary number would otherwise // have to be returned diff --git a/test/gleam_community/maths/metrics_test.gleam b/test/gleam_community/maths/metrics_test.gleam index 43263cb..dcf43eb 100644 --- a/test/gleam_community/maths/metrics_test.gleam +++ b/test/gleam_community/maths/metrics_test.gleam @@ -1,12 +1,12 @@ +import gleam/option +import gleam/set import gleam_community/maths/elementary import gleam_community/maths/metrics import gleam_community/maths/predicates import gleeunit/should -import gleam/set -import gleam/option pub fn float_list_norm_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // An empty lists returns 0.0 [] @@ -66,7 +66,7 @@ pub fn float_list_norm_test() { } pub fn float_list_manhattan_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Empty lists returns an error metrics.manhattan_distance([], [], option.None) @@ -118,7 +118,7 @@ pub fn float_list_manhattan_test() { } pub fn float_list_minkowski_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Empty lists returns an error metrics.minkowski_distance([], [], 1.0, option.None) @@ -226,7 +226,7 @@ pub fn float_list_minkowski_test() { } pub fn float_list_euclidean_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Empty lists returns an error metrics.euclidean_distance([], [], option.None) @@ -410,7 +410,7 @@ pub fn overlap_coefficient_test() { } pub fn cosine_similarity_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Empty lists returns an error metrics.cosine_similarity([], [], option.None) @@ -520,46 +520,6 @@ pub fn chebyshev_distance_test() { |> should.equal(Ok(0.0)) } -pub fn levenshtein_distance_test() { - // Try different types of valid input... - - // Requires 5 insertions to transform the empty string into "hello" - metrics.levenshtein_distance("", "hello") - |> should.equal(5) - // Requires 5 deletions to remove all characters from "hello" to match the empty string - metrics.levenshtein_distance("hello", "") - |> should.equal(5) - - // Requires 2 deletions to remove two 'b's and 1 substitution to change 'b' to 'a' - metrics.levenshtein_distance("bbb", "a") - |> should.equal(3) - // Requires 2 insertions to add two 'b's and 1 substitution to change 'a' to 'b' - metrics.levenshtein_distance("a", "bbb") - |> should.equal(3) - - // No changes needed, since the strings are identical - metrics.levenshtein_distance("hello", "hello") - |> should.equal(0) - - // Requires 1 substitution to change 'a' to 'u' - metrics.levenshtein_distance("cat", "cut") - |> should.equal(1) - - // Requires 2 substitutions (k -> s, e -> i) and 1 insertion (g at the end) - metrics.levenshtein_distance("kitten", "sitting") - |> should.equal(3) - - // Some more complex cases, involving multiple insertions, deletions, and substitutions - metrics.levenshtein_distance("gggtatccat", "cctaggtccct") - |> should.equal(6) - - metrics.levenshtein_distance( - "This is a longer string", - "This is also a much longer string", - ) - |> should.equal(10) -} - pub fn canberra_distance_test() { // Empty lists returns an error metrics.canberra_distance([], [], option.None) diff --git a/test/gleam_community/maths/piecewise_test.gleam b/test/gleam_community/maths/piecewise_test.gleam index de1df0e..11cbc79 100644 --- a/test/gleam_community/maths/piecewise_test.gleam +++ b/test/gleam_community/maths/piecewise_test.gleam @@ -1,8 +1,8 @@ -import gleam_community/maths/piecewise -import gleeunit/should -import gleam/option import gleam/float import gleam/int +import gleam/option +import gleam_community/maths/piecewise +import gleeunit/should pub fn float_ceiling_test() { // Round 3. digit AFTER decimal point diff --git a/test/gleam_community/maths/predicates_test.gleam b/test/gleam_community/maths/predicates_test.gleam index b2e6d57..2244a9a 100644 --- a/test/gleam_community/maths/predicates_test.gleam +++ b/test/gleam_community/maths/predicates_test.gleam @@ -1,5 +1,5 @@ -import gleam_community/maths/predicates import gleam/list +import gleam_community/maths/predicates import gleeunit/should pub fn float_is_close_test() { diff --git a/test/gleam_community/maths/sequences_test.gleam b/test/gleam_community/maths/sequences_test.gleam index ec7afcb..72ee74a 100644 --- a/test/gleam_community/maths/sequences_test.gleam +++ b/test/gleam_community/maths/sequences_test.gleam @@ -1,11 +1,11 @@ +import gleam/list import gleam_community/maths/elementary -import gleam_community/maths/sequences import gleam_community/maths/predicates -import gleam/list +import gleam_community/maths/sequences import gleeunit/should pub fn float_list_linear_space_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values @@ -83,7 +83,7 @@ pub fn float_list_linear_space_test() { } pub fn float_list_logarithmic_space_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values // ---> With endpoint included @@ -147,7 +147,7 @@ pub fn float_list_logarithmic_space_test() { } pub fn float_list_geometric_space_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values // ---> With endpoint included diff --git a/test/gleam_community/maths/special_test.gleam b/test/gleam_community/maths/special_test.gleam index b1d9f51..158c8f0 100644 --- a/test/gleam_community/maths/special_test.gleam +++ b/test/gleam_community/maths/special_test.gleam @@ -1,11 +1,11 @@ +import gleam/result import gleam_community/maths/elementary -import gleam_community/maths/special import gleam_community/maths/predicates +import gleam_community/maths/special import gleeunit/should -import gleam/result pub fn float_beta_function_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Valid input returns a result special.beta(-0.5, 0.5) @@ -26,7 +26,7 @@ pub fn float_beta_function_test() { } pub fn float_error_function_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Valid input returns a result special.erf(-0.5) @@ -51,7 +51,7 @@ pub fn float_error_function_test() { } pub fn float_gamma_function_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Valid input returns a result special.gamma(-0.5) @@ -80,7 +80,7 @@ pub fn float_gamma_function_test() { } pub fn float_incomplete_gamma_function_test() { - let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(tol) = elementary.power(10.0, -6.0) // Invalid input gives an error // 1st arg is invalid