Merge pull request #19 from NicklasXYZ/main

Add distance measures, fix typos, update deps
This commit is contained in:
Nicklas Sindlev Andersen 2024-04-14 17:32:58 +02:00 committed by GitHub
commit d2aa3923bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 988 additions and 286 deletions

View file

@ -3,7 +3,7 @@
packages = [
{ name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
]
[requirements]

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
@ -44,6 +44,9 @@
import gleam/int
import gleam/list
import gleam/option
import gleam/pair
import gleam/result
import gleam_community/maths/conversion
import gleam_community/maths/elementary
import gleam_community/maths/piecewise
@ -54,8 +57,9 @@ import gleam_community/maths/piecewise
/// </a>
/// </div>
///
/// The function calculates the greatest common multiple of two integers $$x, y \in \mathbb{Z}$$.
/// The greatest common multiple is the largest positive integer that is divisible by both $$x$$ and $$y$$.
/// The function calculates the greatest common divisor of two integers
/// $$x, y \in \mathbb{Z}$$. The greatest common divisor is the largest positive
/// integer that is divisible by both $$x$$ and $$y$$.
///
/// <details>
/// <summary>Example:</summary>
@ -64,10 +68,10 @@ import gleam_community/maths/piecewise
/// import gleam_community/maths/arithmetics
///
/// pub fn example() {
/// arithmetics.lcm(1, 1)
/// arithmetics.gcd(1, 1)
/// |> should.equal(1)
///
/// arithmetics.lcm(100, 10)
/// arithmetics.gcd(100, 10)
/// |> should.equal(10)
///
/// arithmetics.gcd(-36, -17)
@ -104,8 +108,9 @@ fn do_gcd(x: Int, y: Int) -> Int {
/// </div>
///
///
/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo of $$x$$ by $$y$$,
/// denoted as $$x \mod y$$, is the remainder $$r$$ of the division of $$x$$ by $$y$$, such that:
/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo
/// of $$x$$ by $$y$$, denoted as $$x \mod y$$, is the remainder $$r$$ of the
/// division of $$x$$ by $$y$$, such that:
///
/// \\[
/// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|,
@ -113,13 +118,15 @@ fn do_gcd(x: Int, y: Int) -> Int {
///
/// where $$q$$ is an integer that represents the quotient of the division.
///
/// The Euclidean modulo function of two numbers, is the remainder operation most commonly utilized in
/// mathematics. This differs from the standard truncating modulo operation frequently employed in
/// programming via the `%` operator. Unlike the `%` operator, which may return negative results
/// depending on the divisor's sign, the Euclidean modulo function is designed to
/// always yield a positive outcome, ensuring consistency with mathematical conventions.
/// The Euclidean modulo function of two numbers, is the remainder operation most
/// commonly utilized in mathematics. This differs from the standard truncating
/// modulo operation frequently employed in programming via the `%` operator.
/// Unlike the `%` operator, which may return negative results depending on the
/// divisor's sign, the Euclidean modulo function is designed to always yield a
/// positive outcome, ensuring consistency with mathematical conventions.
///
/// Note that like the Gleam division operator `/` this will return `0` if one of the arguments is `0`.
/// Note that like the Gleam division operator `/` this will return `0` if one of
/// the arguments is `0`.
///
///
/// <details>
@ -161,8 +168,9 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int {
/// </a>
/// </div>
///
/// The function calculates the least common multiple of two integers $$x, y \in \mathbb{Z}$$.
/// The least common multiple is the smallest positive integer that has both $$x$$ and $$y$$ as factors.
/// The function calculates the least common multiple of two integers
/// $$x, y \in \mathbb{Z}$$. The least common multiple is the smallest positive
/// integer that has both $$x$$ and $$y$$ as factors.
///
/// <details>
/// <summary>Example:</summary>
@ -200,7 +208,8 @@ pub fn lcm(x: Int, y: Int) -> Int {
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, including the number iteself.
/// The function returns all the positive divisors of an integer, including the
/// number itself.
///
/// <details>
/// <summary>Example:</summary>
@ -251,7 +260,8 @@ fn find_divisors(n: Int) -> List(Int) {
/// </a>
/// </div>
///
/// The function returns all the positive divisors of an integer, excluding the number iteself.
/// The function returns all the positive divisors of an integer, excluding the
/// number iteself.
///
/// <details>
/// <summary>Example:</summary>
@ -289,29 +299,32 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a>
/// </div>
///
/// Calculate the sum of the elements in a list:
/// Calculate the (weighted) sum of the elements in a list:
///
/// \\[
/// \sum_{i=1}^n x_i
/// \sum_{i=1}^n w_i x_i
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is
/// the value in the input list indexed by $$i$$, while the $$w_i \in \mathbb{R}$$
/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns an error
/// []
/// |> arithmetics.float_sum()
/// |> arithmetics.float_sum(option.None)
/// |> should.equal(0.0)
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_sum()
/// |> arithmetics.float_sum(option.None)
/// |> should.equal(6.0)
/// }
/// </details>
@ -322,12 +335,18 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a>
/// </div>
///
pub fn float_sum(arr: List(Float)) -> Float {
case arr {
[] -> 0.0
_ ->
pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float {
case arr, weights {
[], _ -> 0.0
_, option.None ->
arr
|> list.fold(0.0, fn(acc: Float, a: Float) -> Float { a +. acc })
_, option.Some(warr) -> {
list.zip(arr, warr)
|> list.fold(0.0, fn(acc: Float, a: #(Float, Float)) -> Float {
pair.first(a) *. pair.second(a) +. acc
})
}
}
}
@ -343,7 +362,8 @@ pub fn float_sum(arr: List(Float)) -> Float {
/// \sum_{i=1}^n x_i
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is
/// the value in the input list indexed by $$i$$.
///
/// <details>
/// <summary>Example:</summary>
@ -385,29 +405,32 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a>
/// </div>
///
/// Calculate the product of the elements in a list:
/// Calculate the (weighted) product of the elements in a list:
///
/// \\[
/// \prod_{i=1}^n x_i
/// \prod_{i=1}^n x_i^{w_i}
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is
/// the value in the input list indexed by $$i$$, while the $$w_i \in \mathbb{R}$$
/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0.0
/// // An empty list returns 1.0
/// []
/// |> arithmetics.float_product()
/// |> should.equal(0.0)
/// |> arithmetics.float_product(option.None)
/// |> should.equal(1.0)
///
/// // Valid input returns a result
/// [1.0, 2.0, 3.0]
/// |> arithmetics.float_product()
/// |> arithmetics.float_product(option.None)
/// |> should.equal(6.0)
/// }
/// </details>
@ -418,12 +441,36 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a>
/// </div>
///
pub fn float_product(arr: List(Float)) -> Float {
case arr {
[] -> 1.0
_ ->
pub fn float_product(
arr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case arr, weights {
[], _ ->
1.0
|> Ok
_, option.None ->
arr
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
|> Ok
_, option.Some(warr) -> {
let results =
list.zip(arr, warr)
|> list.map(fn(a: #(Float, Float)) -> Result(Float, String) {
pair.first(a)
|> elementary.power(pair.second(a))
})
|> result.all
case results {
Ok(prods) ->
prods
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc })
|> Ok
Error(msg) ->
msg
|> Error
}
}
}
}
@ -439,7 +486,8 @@ pub fn float_product(arr: List(Float)) -> Float {
/// \prod_{i=1}^n x_i
/// \\]
///
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is
/// the value in the input list indexed by $$i$$.
///
/// <details>
/// <summary>Example:</summary>
@ -448,10 +496,10 @@ pub fn float_product(arr: List(Float)) -> Float {
/// import gleam_community/maths/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0
/// // An empty list returns 1
/// []
/// |> arithmetics.int_product()
/// |> should.equal(0)
/// |> should.equal(1)
///
/// // Valid input returns a result
/// [1, 2, 3]
@ -487,9 +535,10 @@ pub fn int_product(arr: List(Int)) -> Int {
/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$
/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the
/// sum of the $$1$$ to $$j$$ first elements in the given list.
///
/// <details>
/// <summary>Example:</summary>
@ -536,9 +585,10 @@ pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) {
/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$
/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$
/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the
/// sum of the $$1$$ to $$j$$ first elements in the given list.
///
/// <details>
/// <summary>Example:</summary>
@ -585,9 +635,11 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
/// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// $$n$$ elements. That is, $$n$$ is the length of the list and
/// $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. The
/// value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the
/// given list.
///
/// <details>
/// <summary>Example:</summary>
@ -614,7 +666,7 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) {
/// </a>
/// </div>
///
pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) {
pub fn float_cumulative_product(arr: List(Float)) -> List(Float) {
case arr {
[] -> []
_ ->
@ -635,9 +687,11 @@ pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) {
/// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n
/// \\]
///
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements.
/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$.
/// The value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the given list.
/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of
/// $$n$$ elements. That is, $$n$$ is the length of the list and
/// $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. The
/// value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the
/// given list.
///
/// <details>
/// <summary>Example:</summary>

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
@ -33,11 +33,14 @@
//// * [`chebyshev_distance`](#chebyshev_distance)
//// * [`minkowski_distance`](#minkowski_distance)
//// * [`cosine_similarity`](#cosine_similarity)
//// * [`canberra_distance`](#canberra_distance)
//// * [`braycurtis_distance`](#braycurtis_distance)
//// * **Set & string similarity measures**
//// * [`jaccard_index`](#jaccard_index)
//// * [`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)
@ -56,6 +59,62 @@ 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)'
/// as input
fn validate_lists(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Bool, String) {
case xarr, yarr {
[], _ ->
"Invalid input argument: The list xarr is empty."
|> Error
_, [] ->
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
let xarr_length: Int = list.length(xarr)
let yarr_length: Int = list.length(yarr)
case xarr_length == yarr_length, weights {
False, _ ->
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)."
|> Error
True, option.None -> {
True
|> Ok
}
True, option.Some(warr) -> {
let warr_length: Int = list.length(warr)
case xarr_length == warr_length {
True -> {
validate_weights(warr)
}
False ->
"Invalid input argument: length(weights) != length(xarr) and length(weights) != length(yarr). Valid input is when length(weights) == length(xarr) == length(yarr)."
|> Error
}
}
}
}
}
}
fn validate_weights(warr: List(Float)) -> Result(Bool, String) {
// Check that all the given weights are positive
let assert Ok(minimum) = piecewise.list_minimum(warr, float.compare)
case minimum >=. 0.0 {
False ->
"Invalid input argument: One or more weights are negative. Valid input is when all weights are >= 0."
|> Error
True ->
True
|> Ok
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
@ -63,33 +122,39 @@ import gleam/string
/// </a>
/// </div>
///
/// Calculate the $$p$$-norm of a list (representing a vector):
/// Calculate the (weighted) $$p$$-norm of a list (representing a vector):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i\right|^{p} \right)^{\frac{1}{p}}
/// \left( \sum_{i=1}^n w_{i} \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$$.
/// the input list indexed by $$i$$, while $$w_i \in \mathbb{R}_{+}$$ is
/// a corresponding positive weight ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// let assert Ok(result) =
/// [1.0, 1.0, 1.0]
/// |> metrics.norm(1.0)
/// |> metrics.norm(1.0, option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
///
/// let assert Ok(result) =
/// [1.0, 1.0, 1.0]
/// |> metrics.norm(-1.0)
/// |> metrics.norm(-1.0, option.None)
/// result
/// |> predicates.is_close(0.3333333333333333, 0.0, tol)
/// |> should.be_true()
/// }
@ -101,19 +166,65 @@ import gleam/string
/// </a>
/// </div>
///
pub fn norm(arr: List(Float), p: Float) -> Float {
case arr {
[] -> 0.0
_ -> {
let agg: Float =
pub fn norm(
arr: List(Float),
p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case arr, weights {
[], _ ->
0.0
|> Ok
_, option.None -> {
let aggregate: Float =
arr
|> list.fold(0.0, fn(acc: Float, a: Float) -> Float {
|> list.fold(0.0, fn(accumulator: Float, element: Float) -> Float {
let assert Ok(result) =
elementary.power(piecewise.float_absolute_value(a), p)
result +. acc
piecewise.float_absolute_value(element)
|> elementary.power(p)
result +. accumulator
})
let assert Ok(result) = elementary.power(agg, 1.0 /. p)
let assert Ok(result) = elementary.power(aggregate, 1.0 /. p)
result
|> Ok
}
_, option.Some(warr) -> {
let arr_length: Int = list.length(arr)
let warr_length: Int = list.length(warr)
case arr_length == warr_length {
True -> {
case validate_weights(warr) {
Ok(_) -> {
let tuples: List(#(Float, Float)) = list.zip(arr, warr)
let aggregate: Float =
tuples
|> list.fold(
0.0,
fn(accumulator: Float, tuple: #(Float, Float)) -> Float {
let first_element: Float = pair.first(tuple)
let second_element: Float = pair.second(tuple)
let assert Ok(result) =
elementary.power(
piecewise.float_absolute_value(first_element),
p,
)
second_element *. result +. accumulator
},
)
let assert Ok(result) = elementary.power(aggregate, 1.0 /. p)
result
|> Ok
}
Error(msg) ->
msg
|> Error
}
}
False -> {
"Invalid input argument: length(weights) != length(arr). Valid input is when length(weights) == length(arr)."
|> Error
}
}
}
}
}
@ -124,35 +235,40 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
/// </a>
/// </div>
///
/// Calculate the Manhattan distance between two lists (representing vectors):
/// Calculate the (weighted) Manhattan distance between two lists (representing
/// vectors):
///
/// \\[
/// \sum_{i=1}^n \left|x_i - y_i \right|
/// \sum_{i=1}^n w_{i} \left|x_i - y_i \right|
/// \\]
///
/// 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$$.
/// values in the respective input lists indexed by $$i$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.manhattan_distance([], [])
/// metrics.manhattan_distance([], [], option.None)
/// |> should.be_error()
///
/// // Differing lengths returns error
/// metrics.manhattan_distance([], [1.0])
/// metrics.manhattan_distance([], [1.0], option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0])
/// let assert Ok(result) =
/// metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
@ -168,8 +284,9 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
pub fn manhattan_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 1.0)
minkowski_distance(xarr, yarr, 1.0, weights)
}
/// <div style="text-align: right;">
@ -178,14 +295,17 @@ pub fn manhattan_distance(
/// </a>
/// </div>
///
/// Calculate the Minkowski distance between two lists (representing vectors):
/// Calculate the (weighted) Minkowski distance between two lists (representing
/// vectors):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}}
/// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}}
/// \\]
///
/// In the formula, $$p >= 1$$ is the order, $$n$$ is the length of the two lists
/// and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i$$.
/// The $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// The Minkowski distance is a generalization of both the Euclidean distance
/// ($$p=2$$) and the Manhattan distance ($$p = 1$$).
@ -194,26 +314,28 @@ pub fn manhattan_distance(
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.minkowski_distance([], [], 1.0)
/// metrics.minkowski_distance([], [], 1.0, option.None)
/// |> should.be_error()
///
/// // Differing lengths returns error
/// metrics.minkowski_distance([], [1.0], 1.0)
/// 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)
/// metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0, option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0)
/// let assert Ok(result) =
/// metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0, option.None)
/// result
/// |> predicates.is_close(3.0, 0.0, tol)
/// |> should.be_true()
@ -230,32 +352,26 @@ pub fn minkowski_distance(
xarr: List(Float),
yarr: List(Float),
p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case xarr, yarr {
[], _ ->
"Invalid input argument: The list xarr is empty."
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
_, [] ->
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
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 ->
Ok(_) -> {
case p <. 1.0 {
True ->
"Invalid input argument: p < 1. Valid input is p >= 1."
|> Error
False ->
False -> {
let differences: List(Float) =
list.zip(xarr, yarr)
|> list.map(fn(tuple: #(Float, Float)) -> Float {
pair.first(tuple) -. pair.second(tuple)
})
|> norm(p)
let assert Ok(result) = norm(differences, p, weights)
result
|> Ok
}
}
@ -269,35 +385,40 @@ pub fn minkowski_distance(
/// </a>
/// </div>
///
/// Calculate the Euclidean distance between two lists (representing vectors):
/// Calculate the (weighted) Euclidean distance between two lists (representing
/// vectors):
///
/// \\[
/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}}
/// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}}
/// \\]
///
/// 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$$.
/// values in the respective input lists indexed by $$i$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/elementary
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// pub fn example() {
/// let assert Ok(tol) = elementary.power(-10.0, -6.0)
///
/// // Empty lists returns an error
/// metrics.euclidean_distance([], [])
/// metrics.euclidean_distance([], [], option.None)
/// |> should.be_error()
///
/// // Differing lengths returns an error
/// metrics.euclidean_distance([], [1.0])
/// metrics.euclidean_distance([], [1.0], option.None)
/// |> should.be_error()
///
/// let assert Ok(result) = metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0])
/// let assert Ok(result) =
/// metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
/// result
/// |> predicates.is_close(2.23606797749979, 0.0, tol)
/// |> should.be_true()
@ -313,8 +434,9 @@ pub fn minkowski_distance(
pub fn euclidean_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 2.0)
minkowski_distance(xarr, yarr, 2.0, weights)
}
/// <div style="text-align: right;">
@ -340,7 +462,7 @@ pub fn euclidean_distance(
/// import gleam_community/maths/metrics
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// pub fn example() {
/// // Empty lists returns an error
/// metrics.chebyshev_distance([], [])
/// |> should.be_error()
@ -364,33 +486,19 @@ pub fn chebyshev_distance(
xarr: List(Float),
yarr: List(Float),
) -> Result(Float, String) {
case xarr, yarr {
[], _ ->
"Invalid input argument: The list xarr is empty."
case validate_lists(xarr, yarr, option.None) {
Error(msg) ->
msg
|> Error
_, [] ->
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
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 -> {
let differences =
Ok(_) -> {
list.zip(xarr, yarr)
|> list.map(fn(tuple: #(Float, Float)) -> Float {
{ pair.first(tuple) -. pair.second(tuple) }
|> piecewise.float_absolute_value()
})
differences
|> piecewise.list_maximum(float.compare)
}
}
}
}
}
/// <div style="text-align: right;">
@ -440,7 +548,7 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) {
|> Error
_ ->
arr
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float {
a /. conversion.int_to_float(list.length(arr))
}
@ -578,7 +686,7 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
let assert Ok(result) = elementary.power(a -. mean, 2.0)
result
})
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float {
a
/. {
@ -922,39 +1030,44 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
/// </a>
/// </div>
///
/// Calculate the cosine similarity between two lists (representing vectors):
/// Calculate the (weighted) cosine similarity between two lists (representing
/// vectors):
///
/// \\[
/// \frac{\sum_{i=1}^n x_i \cdot y_i}{\left(\sum_{i=1}^n x_i^2\right)^{\frac{1}{2}}
/// \cdot \left(\sum_{i=1}^n y_i^2\right)^{\frac{1}{2}}}
/// \frac{\sum_{i=1}^n w_{i} \cdot x_i \cdot y_i}
/// {\left(\sum_{i=1}^n w_{i} \cdot x_i^2\right)^{\frac{1}{2}}
/// \cdot
/// \left(\sum_{i=1}^n w_{i} \cdot y_i^2\right)^{\frac{1}{2}}}
/// \\; \in \\; \left[-1, 1\right]
/// \\]
///
/// 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$$. The numerator
/// represents the dot product of the two vectors, while the denominator is the
/// product of the magnitudes (Euclidean norms) of the two vectors. The cosine
/// similarity provides a value between -1 and 1, where 1 means the vectors are
/// in the same direction, -1 means they are in exactly opposite directions,
/// and 0 indicates orthogonality.
/// the values in the respective input lists indexed by $$i$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// The cosine similarity provides a value between -1 and 1, where 1 means the
/// vectors are in the same direction, -1 means they are in exactly opposite
/// directions, and 0 indicates orthogonality.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example () {
/// pub fn example() {
/// // Two orthogonal vectors
/// metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0])
/// metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0], option.None)
/// |> should.equal(Ok(0.0))
///
/// // Two identical (parallel) vectors
/// metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
/// metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
/// |> should.equal(Ok(1.0))
///
/// // Two parallel, but oppositely oriented vectors
/// metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0])
/// metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0], option.None)
/// |> should.equal(Ok(-1.0))
/// }
/// </details>
@ -968,31 +1081,46 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
pub fn cosine_similarity(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case xarr, yarr {
[], _ ->
"Invalid input argument: The list xarr is empty."
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
_, [] ->
"Invalid input argument: The list yarr is empty."
|> Error
_, _ -> {
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 -> {
list.fold(
list.zip(xarr, yarr),
0.0,
fn(acc: Float, a: #(Float, Float)) -> Float {
let result: Float = pair.first(a) *. pair.second(a)
result +. acc
},
)
/. { norm(xarr, 2.0) *. norm(yarr, 2.0) }
Ok(_) -> {
let zipped_arr: List(#(Float, Float)) = list.zip(xarr, yarr)
let numerator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
pair.first(tuple) *. pair.second(tuple)
})
case weights {
option.None -> {
let numerator: Float =
numerator_elements
|> arithmetics.float_sum(option.None)
let assert Ok(xarr_norm) = norm(xarr, 2.0, option.None)
let assert Ok(yarr_norm) = norm(yarr, 2.0, option.None)
let denominator: Float = {
xarr_norm *. yarr_norm
}
numerator /. denominator
|> Ok
}
_ -> {
let numerator: Float =
numerator_elements
|> arithmetics.float_sum(weights)
let assert Ok(xarr_norm) = norm(xarr, 2.0, weights)
let assert Ok(yarr_norm) = norm(yarr, 2.0, weights)
let denominator: Float = {
xarr_norm *. yarr_norm
}
numerator /. denominator
|> Ok
}
}
@ -1014,8 +1142,8 @@ pub fn cosine_similarity(
/// - deletions
/// - substitutions
///
/// Note: The implementation is primarily based on the elixir implementation
/// [https://hex.pm/packages/levenshtein](levenshtein).
/// Note: The implementation is primarily based on the Elixir implementation
/// [levenshtein](https://hex.pm/packages/levenshtein).
///
/// <details>
/// <summary>Example:</summary>
@ -1118,3 +1246,197 @@ fn distance_list_helper(
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Calculate the (weighted) Canberra distance between two lists:
///
/// \\[
/// \sum_{i=1}^n w_{i}\frac{\left| x_i - y_i \right|}
/// {\left| x_i \right| + \left| y_i \right|}
/// \\]
///
/// 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$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example() {
/// // Empty lists returns an error
/// metrics.canberra_distance([], [], option.None)
/// |> should.be_error()
///
/// // Different sized lists returns an error
/// metrics.canberra_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
/// |> should.be_error()
///
/// // Valid inputs
/// metrics.canberra_distance([1.0, 2.0], [-2.0, -1.0], option.None)
/// |> should.equal(Ok(2.0))
///
/// metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 0.5]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
///
pub fn canberra_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
let arr: List(Float) =
list.zip(xarr, yarr)
|> list.map(canberra_distance_helper)
case weights {
option.None -> {
arr
|> arithmetics.float_sum(option.None)
|> Ok
}
_ -> {
arr
|> arithmetics.float_sum(weights)
|> Ok
}
}
}
}
}
fn canberra_distance_helper(tuple: #(Float, Float)) -> Float {
let numerator: Float =
piecewise.float_absolute_value({ pair.first(tuple) -. pair.second(tuple) })
let denominator: Float = {
piecewise.float_absolute_value(pair.first(tuple))
+. piecewise.float_absolute_value(pair.second(tuple))
}
numerator /. denominator
}
/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Calculate the (weighted) Bray-Curtis distance between two lists:
///
/// \\[
/// \frac{\sum_{i=1}^n w_{i} \left| x_i - y_i \right|}
/// {\sum_{i=1}^n w_{i}\left| x_i + y_i \right|}
/// \\]
///
/// 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$$, while the
/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights
/// ($$w_i = 1.0\\;\forall i=1...n$$ by default).
///
/// The Bray-Curtis distance is in the range $$[0, 1]$$ if all entries $$x_i, y_i$$ are
/// positive.
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/metrics
///
/// pub fn example() {
/// // Empty lists returns an error
/// metrics.braycurtis_distance([], [], option.None)
/// |> should.be_error()
///
/// // Different sized lists returns an error
/// metrics.braycurtis_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
/// |> should.be_error()
///
/// // Valid inputs
/// metrics.braycurtis_distance([1.0, 0.0], [0.0, 2.0], option.None)
/// |> should.equal(Ok(1.0))
///
/// metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.5, 1.0]))
/// |> should.equal(Ok(0.375))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
///
pub fn braycurtis_distance(
xarr: List(Float),
yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) {
case validate_lists(xarr, yarr, weights) {
Error(msg) ->
msg
|> Error
Ok(_) -> {
let zipped_arr: List(#(Float, Float)) = list.zip(xarr, yarr)
let numerator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
piecewise.float_absolute_value({
pair.first(tuple) -. pair.second(tuple)
})
})
let denominator_elements: List(Float) =
zipped_arr
|> list.map(fn(tuple: #(Float, Float)) -> Float {
piecewise.float_absolute_value({
pair.first(tuple) +. pair.second(tuple)
})
})
case weights {
option.None -> {
let numerator =
numerator_elements
|> arithmetics.float_sum(option.None)
let denominator =
denominator_elements
|> arithmetics.float_sum(option.None)
{ numerator /. denominator }
|> Ok
}
_ -> {
let numerator =
numerator_elements
|> arithmetics.float_sum(weights)
let denominator =
denominator_elements
|> arithmetics.float_sum(weights)
{ numerator /. denominator }
|> Ok
}
}
}
}
}

View file

@ -1,6 +1,6 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.js" integrity="sha384-cpW21h6RZv/phavutF+AuVYrr+dA8xD9zs6FwLpaCct6O9ctzYFfFr4dgmgccOTx" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
@ -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 \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 \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}.
/// \\]
///
/// The function takes an input $$x$$ and returns a positive integer value.
@ -592,7 +592,8 @@ pub fn float_absolute_difference(a: Float, b: Float) -> Float {
/// \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.
/// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer
/// value which is the the absolute difference of the inputs.
///
/// <details>
/// <summary>Example:</summary>
@ -698,7 +699,8 @@ fn do_int_sign(a: Int) -> Int
/// </a>
/// </div>
///
/// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$ such that it has the same sign as $$y$$.
/// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$
/// such that it has the same sign as $$y$$.
///
/// <div style="text-align: right;">
/// <a href="#">
@ -723,7 +725,8 @@ pub fn float_copy_sign(x: Float, y: Float) -> Float {
/// </a>
/// </div>
///
/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$ such that it has the same sign as $$y$$.
/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$
/// such that it has the same sign as $$y$$.
///
/// <div style="text-align: right;">
/// <a href="#">

View file

@ -23,7 +23,8 @@
////
//// ---
////
//// Predicates: A module containing functions for testing various mathematical properties of numbers.
//// Predicates: A module containing functions for testing various mathematical
//// properties of numbers.
////
//// * **Tests**
//// * [`is_close`](#is_close)
@ -50,8 +51,9 @@ import gleam_community/maths/arithmetics
/// </div>
///
/// Determine if a given value $$a$$ is close to or equivalent to a reference value
/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance values.
/// The equivalance of the two given values are then determined based on the equation:
/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance
/// values. The equivalance of the two given values are then determined based on
/// the equation:
///
/// \\[
/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|)
@ -109,7 +111,8 @@ fn float_absolute_difference(a: Float, b: Float) -> Float {
/// </a>
/// </div>
///
/// Determine if a list of values are close to or equivalent to a another list of reference values.
/// Determine if a list of values are close to or equivalent to a another list of
/// reference values.
///
/// <details>
/// <summary>Example:</summary>
@ -176,7 +179,8 @@ pub fn all_close(
///
/// Determine if a given value is fractional.
///
/// `True` is returned if the given value is fractional, otherwise `False` is returned.
/// `True` is returned if the given value is fractional, otherwise `False` is
/// returned.
///
/// <details>
/// <summary>Example</summary>
@ -213,7 +217,8 @@ fn do_ceiling(a: Float) -> Float
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a power of another integer value $$y \in \mathbb{Z}$$.
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a
/// power of another integer value $$y \in \mathbb{Z}$$.
///
/// <details>
/// <summary>Example:</summary>
@ -252,7 +257,9 @@ pub fn is_power(x: Int, y: Int) -> Bool {
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a perfect number. A number is perfect if it is equal to the sum of its proper positive divisors.
/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a
/// perfect number. A number is perfect if it is equal to the sum of its proper
/// positive divisors.
///
/// <details>
/// <summary>Details</summary>
@ -369,13 +376,16 @@ pub fn is_odd(x: Int) -> Bool {
/// </a>
/// </div>
///
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a prime number.
/// A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself.
/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a
/// prime number. A prime number is a natural number greater than 1 that has no
/// positive divisors other than 1 and itself.
///
/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime. It is a probabilistic
/// test, so it can mistakenly identify a composite number as prime. However, the probability of such errors decreases
/// with more testing iterations (the function uses 64 iterations internally, which is typically more than sufficient).
/// The Miller-Rabin test is particularly useful for large numbers.
/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime.
/// It is a probabilistic test, so it can mistakenly identify a composite number
/// as prime. However, the probability of such errors decreases with more testing
/// iterations (the function uses 64 iterations internally, which is typically
/// more than sufficient). The Miller-Rabin test is particularly useful for large
/// numbers.
///
/// <details>
/// <summary>Details</summary>

View file

@ -23,7 +23,8 @@
////
//// ---
////
//// Sequences: A module containing functions for generating various types of sequences, ranges and intervals.
//// Sequences: A module containing functions for generating various types of
//// sequences, ranges and intervals.
////
//// * **Ranges and intervals**
//// * [`arange`](#arange)
@ -42,8 +43,10 @@ import gleam/list
/// </a>
/// </div>
///
/// The function returns a list with evenly spaced values within a given interval based on a start, stop value and a given increment (step-length) between consecutive values.
/// The list returned includes the given start value but excludes the stop value.
/// The function returns a list with evenly spaced values within a given interval
/// based on a start, stop value and a given increment (step-length) between
/// consecutive values. The list returned includes the given start value but
/// excludes the stop value.
///
/// <details>
/// <summary>Example:</summary>
@ -98,7 +101,8 @@ pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) {
/// </a>
/// </div>
///
/// Generate a linearly spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded.
/// Generate a linearly spaced list of points over a specified interval. The
/// endpoint of the interval can optionally be included/excluded.
///
/// <details>
/// <summary>Example:</summary>
@ -176,7 +180,8 @@ pub fn linear_space(
/// </a>
/// </div>
///
/// Generate a logarithmically spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded.
/// Generate a logarithmically spaced list of points over a specified interval. The
/// endpoint of the interval can optionally be included/excluded.
///
/// <details>
/// <summary>Example:</summary>
@ -236,8 +241,11 @@ pub fn logarithmic_space(
/// </a>
/// </div>
///
/// The function returns a list of numbers spaced evenly on a log scale (a geometric progression). Each point in the list is a constant multiple of the previous.
/// The function is similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints specified directly.
/// The function returns a list of numbers spaced evenly on a log scale (a
/// geometric progression). Each point in the list is a constant multiple of the
/// previous. The function is similar to the
/// [`logarithmic_space`](#logarithmic_space) function, but with endpoints
/// specified directly.
///
/// <details>
/// <summary>Example:</summary>
@ -283,7 +291,7 @@ pub fn geometric_space(
) -> Result(List(Float), String) {
case start == 0.0 || stop == 0.0 {
True ->
""
"Invalid input: Neither 'start' nor 'stop' can be zero, as they must be non-zero for logarithmic calculations."
|> Error
False ->
case num > 0 {

View file

@ -100,8 +100,8 @@ pub fn erf(x: Float) -> Float {
/// </a>
/// </div>
///
/// The gamma function over the real numbers. The function is essentially equal to the
/// factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$
/// The gamma function over the real numbers. The function is essentially equal to
/// the factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$
///
/// The implemented gamma function is approximated through Lanczos approximation
/// using the same coefficients used by the GNU Scientific Library.

View file

@ -1,5 +1,6 @@
import gleam_community/maths/arithmetics
import gleeunit/should
import gleam/option
pub fn int_gcd_test() {
arithmetics.gcd(1, 1)
@ -100,16 +101,16 @@ pub fn int_divisors_test() {
pub fn float_list_sum_test() {
// An empty list returns 0
[]
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> should.equal(0.0)
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> should.equal(6.0)
[-2.0, 4.0, 6.0]
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> should.equal(8.0)
}
@ -132,17 +133,17 @@ pub fn int_list_sum_test() {
pub fn float_list_product_test() {
// An empty list returns 0
[]
|> arithmetics.float_product()
|> should.equal(1.0)
|> arithmetics.float_product(option.None)
|> should.equal(Ok(1.0))
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_product()
|> should.equal(6.0)
|> arithmetics.float_product(option.None)
|> should.equal(Ok(6.0))
[-2.0, 4.0, 6.0]
|> arithmetics.float_product()
|> should.equal(-48.0)
|> arithmetics.float_product(option.None)
|> should.equal(Ok(-48.0))
}
pub fn int_list_product_test() {
@ -196,16 +197,16 @@ pub fn int_list_cumulative_sum_test() {
pub fn float_list_cumulative_product_test() {
// An empty lists returns an empty list
[]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([])
// Valid input returns a result
[1.0, 2.0, 3.0]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([1.0, 2.0, 6.0])
[-2.0, 4.0, 6.0]
|> arithmetics.float_cumumlative_product()
|> arithmetics.float_cumulative_product()
|> should.equal([-2.0, -8.0, -48.0])
}

View file

@ -3,49 +3,64 @@ 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)
// An empty lists returns 0.0
[]
|> metrics.norm(1.0)
|> should.equal(0.0)
|> metrics.norm(1.0, option.None)
|> should.equal(Ok(0.0))
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) =
[1.0, 1.0, 1.0]
|> metrics.norm(1.0)
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[1.0, 1.0, 1.0]
|> metrics.norm(-1.0)
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> metrics.norm(-1.0)
|> metrics.norm(-1.0, option.None)
result
|> predicates.is_close(0.3333333333333333, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -1.0, -1.0]
|> metrics.norm(1.0)
|> metrics.norm(1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(-10.0)
|> metrics.norm(-10.0, option.None)
result
|> predicates.is_close(0.9999007044905545, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(-100.0)
|> metrics.norm(-100.0, option.None)
result
|> predicates.is_close(1.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
[-1.0, -2.0, -3.0]
|> metrics.norm(2.0)
|> metrics.norm(2.0, option.None)
result
|> predicates.is_close(3.7416573867739413, 0.0, tol)
|> should.be_true()
}
@ -54,92 +69,222 @@ pub fn float_list_manhattan_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error
metrics.manhattan_distance([], [])
metrics.manhattan_distance([], [], option.None)
|> should.be_error()
// Differing lengths returns error
metrics.manhattan_distance([], [1.0])
metrics.manhattan_distance([], [1.0], option.None)
|> should.be_error()
// manhattan distance (p = 1)
let assert Ok(result) = metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0])
// Try with valid input (same as Minkowski distance with p = 1)
let assert Ok(result) =
metrics.manhattan_distance([0.0, 0.0], [1.0, 2.0], option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
metrics.manhattan_distance([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
|> should.equal(Ok(9.0))
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
|> should.equal(Ok(9.0))
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
|> should.equal(Ok(18.0))
// Try invalid input with weights (different sized lists returns an error)
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.manhattan_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn float_list_minkowski_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error
metrics.minkowski_distance([], [], 1.0)
metrics.minkowski_distance([], [], 1.0, option.None)
|> should.be_error()
// Differing lengths returns error
metrics.minkowski_distance([], [1.0], 1.0)
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)
metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0, option.None)
|> should.be_error()
// Check that the function agrees, at some arbitrary input
// points, with known function values
let assert Ok(result) =
metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0)
metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0, option.None)
result
|> predicates.is_close(0.0, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0)
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result
|> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0)
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0, option.None)
result
|> predicates.is_close(1.0069555500567189, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0)
metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0, option.None)
result
|> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true()
// Euclidean distance (p = 2)
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0)
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0, option.None)
result
|> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Manhattan distance (p = 1)
let assert Ok(result) =
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0)
metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0, option.None)
result
|> predicates.is_close(3.0, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.None,
)
result
|> predicates.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(3.9482220388574776, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
4.0,
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(4.6952537402198615, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
2.0,
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.minkowski_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
2.0,
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn float_list_euclidean_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error
metrics.euclidean_distance([], [])
metrics.euclidean_distance([], [], option.None)
|> should.be_error()
// Differing lengths returns error
metrics.euclidean_distance([], [1.0])
metrics.euclidean_distance([], [1.0], option.None)
|> should.be_error()
// Euclidean distance (p = 2)
let assert Ok(result) = metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0])
// Try with valid input (same as Minkowski distance with p = 2)
let assert Ok(result) =
metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0], option.None)
result
|> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true()
// Try different valid input
let assert Ok(result) =
metrics.euclidean_distance([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
result
|> predicates.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(5.196152422706632, 0.0, tol)
|> should.be_true()
let assert Ok(result) =
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(7.3484692283495345, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.euclidean_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn mean_test() {
@ -265,33 +410,80 @@ pub fn overlap_coefficient_test() {
}
pub fn cosine_similarity_test() {
let assert Ok(tol) = elementary.power(-10.0, -6.0)
// Empty lists returns an error
metrics.cosine_similarity([], [])
metrics.cosine_similarity([], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.cosine_similarity([1.0, 2.0, 3.0], [])
metrics.cosine_similarity([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.cosine_similarity([], [1.0, 2.0, 3.0])
metrics.cosine_similarity([], [1.0, 2.0, 3.0], option.None)
|> should.be_error()
// Different sized lists returns an error
metrics.cosine_similarity([1.0, 2.0], [1.0, 2.0, 3.0, 4.0])
metrics.cosine_similarity([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error()
// Two orthogonal vectors (represented by lists)
metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0])
metrics.cosine_similarity([-1.0, 1.0, 0.0], [1.0, 1.0, -1.0], option.None)
|> should.equal(Ok(0.0))
// Two identical (parallel) vectors (represented by lists)
metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0])
metrics.cosine_similarity([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
|> should.equal(Ok(1.0))
// Two parallel, but oppositely oriented vectors (represented by lists)
metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0])
metrics.cosine_similarity([-1.0, -2.0, -3.0], [1.0, 2.0, 3.0], option.None)
|> should.equal(Ok(-1.0))
// Try with arbitrary valid input
let assert Ok(result) =
metrics.cosine_similarity([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], option.None)
result
|> predicates.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try valid input with weights
let assert Ok(result) =
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 1.0, 1.0]),
)
result
|> predicates.is_close(0.9746318461970762, 0.0, tol)
|> should.be_true()
// Try with different weights
let assert Ok(result) =
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([1.0, 2.0, 3.0]),
)
result
|> predicates.is_close(0.9855274566525745, 0.0, tol)
|> should.be_true()
// Try invalid input with weights (different sized lists returns an error)
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([7.0, 8.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.cosine_similarity(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn chebyshev_distance_test() {
@ -367,3 +559,115 @@ pub fn levenshtein_distance_test() {
)
|> should.equal(10)
}
pub fn canberra_distance_test() {
// Empty lists returns an error
metrics.canberra_distance([], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.canberra_distance([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.canberra_distance([], [1.0, 2.0, 3.0], option.None)
|> should.be_error()
// Different sized lists returns an error
metrics.canberra_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error()
// Try different types of valid input
metrics.canberra_distance([0.0, 0.0], [0.0, 0.0], option.None)
|> should.equal(Ok(0.0))
metrics.canberra_distance([1.0, 2.0], [-2.0, -1.0], option.None)
|> should.equal(Ok(2.0))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.None)
|> should.equal(Ok(2.0))
metrics.canberra_distance([1.0, 0.0], [2.0, 0.0], option.None)
|> should.equal(Ok(1.0 /. 3.0))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 1.0]))
|> should.equal(Ok(2.0))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([1.0, 0.5]))
|> should.equal(Ok(1.5))
metrics.canberra_distance([1.0, 0.0], [0.0, 2.0], option.Some([0.5, 0.5]))
|> should.equal(Ok(1.0))
// Different sized lists (weights) returns an error
metrics.canberra_distance(
[1.0, 2.0, 3.0],
[1.0, 2.0, 3.0],
option.Some([1.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.canberra_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}
pub fn braycurtis_distance_test() {
// Empty lists returns an error
metrics.braycurtis_distance([], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.braycurtis_distance([1.0, 2.0, 3.0], [], option.None)
|> should.be_error()
// One empty list returns an error
metrics.braycurtis_distance([], [1.0, 2.0, 3.0], option.None)
|> should.be_error()
// Different sized lists returns an error
metrics.braycurtis_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error()
// Try different types of valid input
metrics.braycurtis_distance([0.0, 0.0], [0.0, 0.0], option.None)
|> should.equal(Ok(0.0))
metrics.braycurtis_distance([1.0, 2.0], [-2.0, -1.0], option.None)
|> should.equal(Ok(3.0))
metrics.braycurtis_distance([1.0, 0.0], [0.0, 2.0], option.None)
|> should.equal(Ok(1.0))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.None)
|> should.equal(Ok(0.4))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([1.0, 1.0]))
|> should.equal(Ok(0.4))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.5, 1.0]))
|> should.equal(Ok(0.375))
metrics.braycurtis_distance([1.0, 2.0], [3.0, 4.0], option.Some([0.25, 0.25]))
|> should.equal(Ok(0.4))
// Different sized lists (weights) returns an error
metrics.braycurtis_distance(
[1.0, 2.0, 3.0],
[1.0, 2.0, 3.0],
option.Some([1.0]),
)
|> should.be_error()
// Try invalid input with weights that are negative
metrics.braycurtis_distance(
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
option.Some([-7.0, -8.0, -9.0]),
)
|> should.be_error()
}