diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67e89ac..ef48f17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,4 +24,6 @@ jobs: - run: gleam format --check - - run: gleam test \ No newline at end of file + - run: gleam test --target=erlang + + - run: gleam test --target=javscript \ No newline at end of file diff --git a/src/gleam_community/maths/float.gleam b/src/gleam_community/maths/float.gleam index 83eeb6b..0df101c 100644 --- a/src/gleam_community/maths/float.gleam +++ b/src/gleam_community/maths/float.gleam @@ -1587,14 +1587,14 @@ pub fn nth_root(x: Float, n: Int) -> Result(Float, String) { "Invalid input argument: x < 0. Valid input is x > 0" |> Error False -> - case n >= 2 { + case n >= 1 { True -> { assert Ok(result) = power(x, 1.0 /. int.to_float(n)) result |> Ok } False -> - "Invalid input argyment: n < 2. Valid input is n >= 2." + "Invalid input argument: n < 1. Valid input is n >= 2." |> Error } } diff --git a/src/gleam_community/maths/float_list.gleam b/src/gleam_community/maths/float_list.gleam index 4a76861..cf10672 100644 --- a/src/gleam_community/maths/float_list.gleam +++ b/src/gleam_community/maths/float_list.gleam @@ -27,11 +27,9 @@ //// * [`sum`](#sum) //// * [`product`](#product) //// * [`norm`](#norm) -//// * [`minkowski`](#minkowski) -//// * [`euclidean`](#euclidean) -//// * [`manhatten`](#manhatten) -//// * [`root_mean_squared_error`](#root_mean_squared_error) -//// * [`mean_squared_error`](#mean_squared_error) +//// * [`minkowski_distance`](#minkowski_distance) +//// * [`euclidean_distance`](#euclidean_distance) +//// * [`manhatten_distance`](#manhatten_distance) //// * [`cumulative_sum`](#cumulative_sum) //// * [`cumulative_product`](#cumulative_product) //// * **Ranges and intervals** @@ -53,6 +51,8 @@ import gleam/float import gleam/pair import gleam/option import gleam_community/maths/float as floatx +import gleam_community/maths/int as intx +import gleam/io ///
/// @@ -60,6 +60,77 @@ import gleam_community/maths/float as floatx /// ///
/// +/// Calculcate the $$p$$-norm of a list (representing a vector): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i\right|^{p} \right)^{\frac{1}{p}} +/// \\] +/// +/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// assert Ok(tol) = floatx.power(-10.0, -6.0) +/// +/// [1.0, 1.0, 1.0] +/// |> float_list.norm(1.0) +/// |> floatx.is_close(3.0, 0.0, tol) +/// |> should.be_true() +/// +/// [1.0, 1.0, 1.0] +/// |> float_list.norm(-1.0) +/// |> floatx.is_close(0.3333333333333333, 0.0, tol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn norm(arr: List(Float), p: Float) -> Float { + case arr { + [] -> 0.0 + _ -> { + let agg: Float = + arr + |> list.fold( + 0.0, + fn(acc: Float, a: Float) -> Float { + assert Ok(result) = floatx.power(float.absolute_value(a), p) + result +. acc + }, + ) + assert Ok(result) = floatx.power(agg, 1.0 /. p) + result + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Minkowski distance between two lists (representing vectors): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i - x_j \right|^{p} \right)^{\frac{1}{p}} +/// \\] +/// +/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +/// The Minkowski distance is a generalization of both the Euclidean distance ($$p=2$$) and the Manhattan distance ($$p = 1$$). +/// ///
/// Example: /// @@ -77,8 +148,69 @@ import gleam_community/maths/float as floatx /// /// /// -pub fn norm(xarr: List(Float), p: Int) -> Float { - todo +pub fn minkowski_distance( + xarr: List(Float), + yarr: List(Float), + p: Float, +) -> Result(Float, String) { + let xlen: Int = list.length(xarr) + let ylen: Int = list.length(yarr) + case xlen == ylen { + False -> + "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." + |> Error + True -> + case p <. 1.0 { + True -> + "Invalid input argument: p < 1. Valid input is p >= 1." + |> Error + False -> + list.zip(xarr, yarr) + |> list.map(fn(tuple: #(Float, Float)) -> Float { + pair.first(tuple) -. pair.second(tuple) + }) + |> norm(p) + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Euclidean distance between two lists (representing vectors): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i - x_j \right|^{p} \right)^{\frac{1}{p}} +/// \\] +/// +/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn euclidean_distance( + xarr: List(Float), + yarr: List(Float), +) -> Result(Float, String) { + minkowski_distance(xarr, yarr, 2.0) } ///
@@ -104,116 +236,11 @@ pub fn norm(xarr: List(Float), p: Int) -> Float { /// ///
/// -pub fn minkowski(xarr: List(Float), yarr: List(Float), p: Int) -> Float { - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn euclidean(xarr: List(Float), yarr: List(Float)) -> Float { - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn manhatten(xarr: List(Float), yarr: List(Float)) -> Float { - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn mean_squared_error(xarr: List(Float), yarr: List(Float)) -> Float { - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn root_mean_squared_error(xarr: List(Float), yarr: List(Float)) -> Float { - todo +pub fn manhatten_distance( + xarr: List(Float), + yarr: List(Float), +) -> Result(Float, String) { + minkowski_distance(xarr, yarr, 1.0) } ///
@@ -659,7 +686,7 @@ pub fn arg_maximum(arr: List(Float)) -> Result(List(Int), String) { arr |> list.index_map(fn(index: Int, a: Float) -> Int { case a -. max { - 0. -> index + 0.0 -> index _ -> -1 } }) @@ -719,7 +746,7 @@ pub fn arg_minimum(arr: List(Float)) -> Result(List(Int), String) { arr |> list.index_map(fn(index: Int, a: Float) -> Int { case a -. min { - 0. -> index + 0.0 -> index _ -> -1 } }) diff --git a/test/gleam/gleam_community_maths_float_list_test.gleam b/test/gleam/gleam_community_maths_float_list_test.gleam index aea7158..717f1c3 100644 --- a/test/gleam/gleam_community_maths_float_list_test.gleam +++ b/test/gleam/gleam_community_maths_float_list_test.gleam @@ -2,8 +2,10 @@ import gleam/int import gleam/list import gleam/pair import gleam_community/maths/float_list +import gleam_community/maths/float as floatx import gleeunit import gleeunit/should +import gleam/io pub fn main() { gleeunit.main() @@ -33,6 +35,98 @@ pub fn float_list_all_close_test() { |> should.equal(Ok(True)) } +pub fn float_list_norm_test() { + assert Ok(tol) = floatx.power(-10.0, -6.0) + + // An empty lists returns 0.0 + [] + |> float_list.norm(1.0) + |> should.equal(0.0) + + // Check that the function agrees, at some arbitrary input + // points, with known function values + [1.0, 1.0, 1.0] + |> float_list.norm(1.0) + |> floatx.is_close(3.0, 0.0, tol) + |> should.be_true() + + [1.0, 1.0, 1.0] + |> float_list.norm(-1.0) + |> floatx.is_close(0.3333333333333333, 0.0, tol) + |> should.be_true() + + [-1.0, -1.0, -1.0] + |> float_list.norm(-1.0) + |> floatx.is_close(0.3333333333333333, 0.0, tol) + |> should.be_true() + + [-1.0, -1.0, -1.0] + |> float_list.norm(1.0) + |> floatx.is_close(3.0, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> float_list.norm(-10.0) + |> floatx.is_close(0.9999007044905545, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> float_list.norm(-100.0) + |> floatx.is_close(1.0, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> float_list.norm(2.0) + |> floatx.is_close(3.7416573867739413, 0.0, tol) + |> should.be_true() +} + +pub fn float_list_minkowski_test() { + assert Ok(tol) = floatx.power(-10.0, -6.0) + + // Empty lists returns 0.0 + float_list.minkowski_distance([], [], 1.0) + |> should.equal(Ok(0.0)) + + // Differing lenghths returns error + float_list.minkowski_distance([], [1.0], 1.0) + |> should.be_error() + + // Test order < 1 + float_list.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) + |> should.be_error() + + // Check that the function agrees, at some arbitrary input + // points, with known function values + assert Ok(result) = float_list.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0) + result + |> floatx.is_close(0.0, 0.0, tol) + |> should.be_true() + + assert Ok(result) = + float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) + result + |> floatx.is_close(1.0717734625362931, 0.0, tol) + |> should.be_true() + + assert Ok(result) = + float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0) + result + |> floatx.is_close(1.0069555500567189, 0.0, tol) + |> should.be_true() + + assert Ok(result) = + float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) + result + |> floatx.is_close(1.0717734625362931, 0.0, tol) + |> should.be_true() + + assert Ok(result) = float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0) + result + |> floatx.is_close(2.23606797749979, 0.0, tol) + |> should.be_true() +} + pub fn float_list_maximum_test() { // An empty lists returns an error []