Add canberra and bary-curtis distance

This commit is contained in:
NicklasXYZ 2024-04-13 23:38:39 +02:00
parent d9c642062a
commit b7f7b29e48
4 changed files with 432 additions and 138 deletions

View file

@ -44,6 +44,9 @@
import gleam/int import gleam/int
import gleam/list import gleam/list
import gleam/option
import gleam/pair
import gleam/result
import gleam_community/maths/conversion import gleam_community/maths/conversion
import gleam_community/maths/elementary import gleam_community/maths/elementary
import gleam_community/maths/piecewise import gleam_community/maths/piecewise
@ -289,29 +292,32 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a> /// </a>
/// </div> /// </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 $$w_i \in \mathbb{R}$$ is
/// a corresponding weight ($$w_i = 1.0\;\forall i=1...n$$ by default).
/// ///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example () { /// pub fn example () {
/// // An empty list returns an error /// // An empty list returns an error
/// [] /// []
/// |> arithmetics.float_sum() /// |> arithmetics.float_sum(option.None)
/// |> should.equal(0.0) /// |> should.equal(0.0)
/// ///
/// // Valid input returns a result /// // Valid input returns a result
/// [1.0, 2.0, 3.0] /// [1.0, 2.0, 3.0]
/// |> arithmetics.float_sum() /// |> arithmetics.float_sum(option.None)
/// |> should.equal(6.0) /// |> should.equal(6.0)
/// } /// }
/// </details> /// </details>
@ -322,12 +328,18 @@ pub fn proper_divisors(n: Int) -> List(Int) {
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn float_sum(arr: List(Float)) -> Float { pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float {
case arr { case arr, weights {
[] -> 0.0 [], _ -> 0.0
_ -> _, option.None ->
arr arr
|> list.fold(0.0, fn(acc: Float, a: Float) -> Float { a +. acc }) |> 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
})
}
} }
} }
@ -385,29 +397,32 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a> /// </a>
/// </div> /// </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 $$w_i \in \mathbb{R}$$ is
/// a corresponding weight ($$w_i = 1.0\;\forall i=1...n$$ by default).
///
/// <details> /// <details>
/// <summary>Example:</summary> /// <summary>Example:</summary>
/// ///
/// import gleeunit/should /// import gleeunit/should
/// import gleam/option
/// import gleam_community/maths/arithmetics /// import gleam_community/maths/arithmetics
/// ///
/// pub fn example () { /// pub fn example () {
/// // An empty list returns 0.0 /// // An empty list returns 0.0
/// [] /// []
/// |> arithmetics.float_product() /// |> arithmetics.float_product(option.None)
/// |> should.equal(0.0) /// |> should.equal(0.0)
/// ///
/// // Valid input returns a result /// // Valid input returns a result
/// [1.0, 2.0, 3.0] /// [1.0, 2.0, 3.0]
/// |> arithmetics.float_product() /// |> arithmetics.float_product(option.None)
/// |> should.equal(6.0) /// |> should.equal(6.0)
/// } /// }
/// </details> /// </details>
@ -418,12 +433,36 @@ pub fn int_sum(arr: List(Int)) -> Int {
/// </a> /// </a>
/// </div> /// </div>
/// ///
pub fn float_product(arr: List(Float)) -> Float { pub fn float_product(
case arr { arr: List(Float),
[] -> 1.0 weights: option.Option(List(Float)),
_ -> ) -> Result(Float, String) {
case arr, weights {
[], _ ->
1.0
|> Ok
_, option.None ->
arr arr
|> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc }) |> 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
}
}
} }
} }

View file

@ -33,6 +33,8 @@
//// * [`chebyshev_distance`](#chebyshev_distance) //// * [`chebyshev_distance`](#chebyshev_distance)
//// * [`minkowski_distance`](#minkowski_distance) //// * [`minkowski_distance`](#minkowski_distance)
//// * [`cosine_similarity`](#cosine_similarity) //// * [`cosine_similarity`](#cosine_similarity)
//// * [`canberra_distance`](#canberra_distance)
//// * [`braycurtis_distance`](#braycurtis_distance)
//// * **Set & string similarity measures** //// * **Set & string similarity measures**
//// * [`jaccard_index`](#jaccard_index) //// * [`jaccard_index`](#jaccard_index)
//// * [`sorensen_dice_coefficient`](#sorensen_dice_coefficient) //// * [`sorensen_dice_coefficient`](#sorensen_dice_coefficient)
@ -57,6 +59,48 @@ import gleam/set
import gleam/float import gleam/float
import gleam/int import gleam/int
import gleam/string import gleam/string
import gleam/option
/// Utility function that checks all lists have the expected length
/// Primarily used by all distance measures taking List(Float) as input
fn check_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 xlen: Int = list.length(xarr)
let ylen: Int = list.length(yarr)
case xlen == ylen, 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 wlen: Int = list.length(warr)
case xlen == wlen {
True ->
True
|> Ok
False ->
"Invalid input argument: length(weights) != length(xarr) and length(weights) != length(yarr). Valid input is when length(weights) == length(xarr) == length(yarr)."
|> Error
}
}
}
}
}
}
/// <div style="text-align: right;"> /// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues"> /// <a href="https://github.com/gleam-community/maths/issues">
@ -169,8 +213,9 @@ pub fn norm(arr: List(Float), p: Float) -> Float {
pub fn manhattan_distance( pub fn manhattan_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 1.0) minkowski_distance(xarr, yarr, 1.0, weights)
} }
/// <div style="text-align: right;"> /// <div style="text-align: right;">
@ -231,34 +276,24 @@ pub fn minkowski_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
p: Float, p: Float,
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case check_lists(xarr, yarr, weights) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." case p <. 1.0 {
|> 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 -> True ->
case p <. 1.0 { "Invalid input argument: p < 1. Valid input is p >= 1."
True -> |> Error
"Invalid input argument: p < 1. Valid input is p >= 1." False ->
|> Error list.zip(xarr, yarr)
False -> |> list.map(fn(tuple: #(Float, Float)) -> Float {
list.zip(xarr, yarr) pair.first(tuple) -. pair.second(tuple)
|> list.map(fn(tuple: #(Float, Float)) -> Float { })
pair.first(tuple) -. pair.second(tuple) |> norm(p)
}) |> Ok
|> norm(p)
|> Ok
}
} }
} }
} }
@ -314,8 +349,9 @@ pub fn minkowski_distance(
pub fn euclidean_distance( pub fn euclidean_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
minkowski_distance(xarr, yarr, 2.0) minkowski_distance(xarr, yarr, 2.0, weights)
} }
/// <div style="text-align: right;"> /// <div style="text-align: right;">
@ -364,32 +400,21 @@ pub fn euclidean_distance(
pub fn chebyshev_distance( pub fn chebyshev_distance(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case check_lists(xarr, yarr, weights) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." let differences =
|> Error list.zip(xarr, yarr)
_, _ -> { |> list.map(fn(tuple: #(Float, Float)) -> Float {
let xlen: Int = list.length(xarr) { pair.first(tuple) -. pair.second(tuple) }
let ylen: Int = list.length(yarr) |> piecewise.float_absolute_value()
case xlen == ylen { })
False -> differences
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." |> piecewise.list_maximum(float.compare)
|> Error
True -> {
let differences =
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)
}
}
} }
} }
} }
@ -441,7 +466,7 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) {
|> Error |> Error
_ -> _ ->
arr arr
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float { |> fn(a: Float) -> Float {
a /. conversion.int_to_float(list.length(arr)) a /. conversion.int_to_float(list.length(arr))
} }
@ -579,7 +604,7 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) {
let assert Ok(result) = elementary.power(a -. mean, 2.0) let assert Ok(result) = elementary.power(a -. mean, 2.0)
result result
}) })
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float { |> fn(a: Float) -> Float {
a a
/. { /. {
@ -969,34 +994,23 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float {
pub fn cosine_similarity( pub fn cosine_similarity(
xarr: List(Float), xarr: List(Float),
yarr: List(Float), yarr: List(Float),
weights: option.Option(List(Float)),
) -> Result(Float, String) { ) -> Result(Float, String) {
case xarr, yarr { case check_lists(xarr, yarr, weights) {
[], _ -> Error(msg) ->
"Invalid input argument: The list xarr is empty." msg
|> Error |> Error
_, [] -> Ok(_) -> {
"Invalid input argument: The list yarr is empty." list.fold(
|> Error list.zip(xarr, yarr),
_, _ -> { 0.0,
let xlen: Int = list.length(xarr) fn(acc: Float, a: #(Float, Float)) -> Float {
let ylen: Int = list.length(yarr) let result: Float = pair.first(a) *. pair.second(a)
case xlen == ylen { result +. acc
False -> },
"Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." )
|> Error /. { norm(xarr, 2.0) *. norm(yarr, 2.0) }
True -> { |> Ok
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
}
}
} }
} }
} }
@ -1119,3 +1133,140 @@ 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>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/metrics
///
/// pub fn example () {
/// }
/// </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 check_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>
///
/// <details>
/// <summary>Example:</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/metrics
///
/// pub fn example () {
/// }
/// </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 check_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,5 +1,6 @@
import gleam_community/maths/arithmetics import gleam_community/maths/arithmetics
import gleeunit/should import gleeunit/should
import gleam/option
pub fn int_gcd_test() { pub fn int_gcd_test() {
arithmetics.gcd(1, 1) arithmetics.gcd(1, 1)
@ -100,16 +101,16 @@ pub fn int_divisors_test() {
pub fn float_list_sum_test() { pub fn float_list_sum_test() {
// An empty list returns 0 // An empty list returns 0
[] []
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(0.0) |> should.equal(0.0)
// Valid input returns a result // Valid input returns a result
[1.0, 2.0, 3.0] [1.0, 2.0, 3.0]
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(6.0) |> should.equal(6.0)
[-2.0, 4.0, 6.0] [-2.0, 4.0, 6.0]
|> arithmetics.float_sum() |> arithmetics.float_sum(option.None)
|> should.equal(8.0) |> should.equal(8.0)
} }
@ -132,17 +133,17 @@ pub fn int_list_sum_test() {
pub fn float_list_product_test() { pub fn float_list_product_test() {
// An empty list returns 0 // An empty list returns 0
[] []
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(1.0) |> should.equal(Ok(1.0))
// Valid input returns a result // Valid input returns a result
[1.0, 2.0, 3.0] [1.0, 2.0, 3.0]
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(6.0) |> should.equal(Ok(6.0))
[-2.0, 4.0, 6.0] [-2.0, 4.0, 6.0]
|> arithmetics.float_product() |> arithmetics.float_product(option.None)
|> should.equal(-48.0) |> should.equal(Ok(-48.0))
} }
pub fn int_list_product_test() { pub fn int_list_product_test() {

View file

@ -3,6 +3,7 @@ import gleam_community/maths/metrics
import gleam_community/maths/predicates import gleam_community/maths/predicates
import gleeunit/should import gleeunit/should
import gleam/set import gleam/set
import gleam/option
pub fn float_list_norm_test() { 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)
@ -54,15 +55,16 @@ 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 // Empty lists returns an error
metrics.manhattan_distance([], []) metrics.manhattan_distance([], [], option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.manhattan_distance([], [1.0]) metrics.manhattan_distance([], [1.0], option.None)
|> should.be_error() |> should.be_error()
// manhattan distance (p = 1) // manhattan distance (p = 1)
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 result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> should.be_true()
@ -72,53 +74,53 @@ 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 // Empty lists returns an error
metrics.minkowski_distance([], [], 1.0) metrics.minkowski_distance([], [], 1.0, option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.minkowski_distance([], [1.0], 1.0) metrics.minkowski_distance([], [1.0], 1.0, option.None)
|> should.be_error() |> should.be_error()
// Test order < 1 // 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() |> should.be_error()
// Check that the function agrees, at some arbitrary input // Check that the function agrees, at some arbitrary input
// points, with known function values // points, with known function values
let assert Ok(result) = 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 result
|> predicates.is_close(0.0, 0.0, tol) |> predicates.is_close(0.0, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = 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 result
|> predicates.is_close(1.0717734625362931, 0.0, tol) |> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = 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 result
|> predicates.is_close(1.0069555500567189, 0.0, tol) |> predicates.is_close(1.0069555500567189, 0.0, tol)
|> should.be_true() |> should.be_true()
let assert Ok(result) = 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 result
|> predicates.is_close(1.0717734625362931, 0.0, tol) |> predicates.is_close(1.0717734625362931, 0.0, tol)
|> should.be_true() |> should.be_true()
// Euclidean distance (p = 2) // Euclidean distance (p = 2)
let assert Ok(result) = 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 result
|> predicates.is_close(2.23606797749979, 0.0, tol) |> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true() |> should.be_true()
// Manhattan distance (p = 1) // Manhattan distance (p = 1)
let assert Ok(result) = 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 result
|> predicates.is_close(3.0, 0.0, tol) |> predicates.is_close(3.0, 0.0, tol)
|> should.be_true() |> should.be_true()
@ -128,15 +130,16 @@ 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 // Empty lists returns an error
metrics.euclidean_distance([], []) metrics.euclidean_distance([], [], option.None)
|> should.be_error() |> should.be_error()
// Differing lengths returns error // Differing lengths returns error
metrics.euclidean_distance([], [1.0]) metrics.euclidean_distance([], [1.0], option.None)
|> should.be_error() |> should.be_error()
// Euclidean distance (p = 2) // Euclidean distance (p = 2)
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 result
|> predicates.is_close(2.23606797749979, 0.0, tol) |> predicates.is_close(2.23606797749979, 0.0, tol)
|> should.be_true() |> should.be_true()
@ -266,65 +269,69 @@ pub fn overlap_coefficient_test() {
pub fn cosine_similarity_test() { pub fn cosine_similarity_test() {
// Empty lists returns an error // Empty lists returns an error
metrics.cosine_similarity([], []) metrics.cosine_similarity([], [], option.None)
|> should.be_error() |> should.be_error()
// One empty list returns an 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() |> should.be_error()
// One empty list returns an 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() |> should.be_error()
// Different sized lists returns an 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() |> should.be_error()
// Two orthogonal vectors (represented by lists) // 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)) |> should.equal(Ok(0.0))
// Two identical (parallel) vectors (represented by lists) // 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)) |> should.equal(Ok(1.0))
// Two parallel, but oppositely oriented vectors (represented by lists) // 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)) |> should.equal(Ok(-1.0))
} }
pub fn chebyshev_distance_test() { pub fn chebyshev_distance_test() {
// Empty lists returns an error // Empty lists returns an error
metrics.chebyshev_distance([], []) metrics.chebyshev_distance([], [], option.None)
|> should.be_error() |> should.be_error()
// One empty list returns an error // One empty list returns an error
metrics.chebyshev_distance([1.0, 2.0, 3.0], []) metrics.chebyshev_distance([1.0, 2.0, 3.0], [], option.None)
|> should.be_error() |> should.be_error()
// One empty list returns an error // One empty list returns an error
metrics.chebyshev_distance([], [1.0, 2.0, 3.0]) metrics.chebyshev_distance([], [1.0, 2.0, 3.0], option.None)
|> should.be_error() |> should.be_error()
// Different sized lists returns an error // Different sized lists returns an error
metrics.chebyshev_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0]) metrics.chebyshev_distance([1.0, 2.0], [1.0, 2.0, 3.0, 4.0], option.None)
|> should.be_error() |> should.be_error()
// Try different types of valid input // Try different types of valid input
metrics.chebyshev_distance([1.0, 0.0], [0.0, 2.0]) metrics.chebyshev_distance([1.0, 0.0], [0.0, 2.0], option.None)
|> should.equal(Ok(2.0)) |> should.equal(Ok(2.0))
metrics.chebyshev_distance([1.0, 0.0], [2.0, 0.0]) metrics.chebyshev_distance([1.0, 0.0], [2.0, 0.0], option.None)
|> should.equal(Ok(1.0)) |> should.equal(Ok(1.0))
metrics.chebyshev_distance([1.0, 0.0], [-2.0, 0.0]) metrics.chebyshev_distance([1.0, 0.0], [-2.0, 0.0], option.None)
|> should.equal(Ok(3.0)) |> should.equal(Ok(3.0))
metrics.chebyshev_distance([-5.0, -10.0, -3.0], [-1.0, -12.0, -3.0]) metrics.chebyshev_distance(
[-5.0, -10.0, -3.0],
[-1.0, -12.0, -3.0],
option.None,
)
|> should.equal(Ok(4.0)) |> should.equal(Ok(4.0))
metrics.chebyshev_distance([1.0, 2.0, 3.0], [1.0, 2.0, 3.0]) metrics.chebyshev_distance([1.0, 2.0, 3.0], [1.0, 2.0, 3.0], option.None)
|> should.equal(Ok(0.0)) |> should.equal(Ok(0.0))
} }
@ -367,3 +374,99 @@ pub fn levenshtein_distance_test() {
) )
|> should.equal(10) |> 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()
}
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()
}