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/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
@ -289,29 +292,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 $$w_i \in \mathbb{R}$$ is
/// a corresponding 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/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 +328,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
})
}
}
}
@ -385,29 +397,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 $$w_i \in \mathbb{R}$$ is
/// a corresponding 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/arithmetics
///
/// pub fn example () {
/// // An empty list returns 0.0
/// []
/// |> arithmetics.float_product()
/// |> arithmetics.float_product(option.None)
/// |> should.equal(0.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 +433,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
}
}
}
}

View file

@ -33,6 +33,8 @@
//// * [`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)
@ -57,6 +59,48 @@ 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
/// 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;">
/// <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(
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;">
@ -231,22 +276,13 @@ 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 check_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."
@ -261,7 +297,6 @@ pub fn minkowski_distance(
}
}
}
}
}
/// <div style="text-align: right;">
@ -314,8 +349,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;">
@ -364,22 +400,13 @@ pub fn euclidean_distance(
pub fn chebyshev_distance(
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 check_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(_) -> {
let differences =
list.zip(xarr, yarr)
|> list.map(fn(tuple: #(Float, Float)) -> Float {
@ -390,8 +417,6 @@ pub fn chebyshev_distance(
|> piecewise.list_maximum(float.compare)
}
}
}
}
}
/// <div style="text-align: right;">
@ -441,7 +466,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))
}
@ -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)
result
})
|> arithmetics.float_sum()
|> arithmetics.float_sum(option.None)
|> fn(a: Float) -> Float {
a
/. {
@ -969,22 +994,13 @@ 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 check_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(_) -> {
list.fold(
list.zip(xarr, yarr),
0.0,
@ -997,8 +1013,6 @@ pub fn cosine_similarity(
|> Ok
}
}
}
}
}
/// <div style="text-align: right;">
@ -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 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() {

View file

@ -3,6 +3,7 @@ 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)
@ -54,15 +55,16 @@ 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])
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()
@ -72,53 +74,53 @@ 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()
@ -128,15 +130,16 @@ 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])
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()
@ -266,65 +269,69 @@ pub fn overlap_coefficient_test() {
pub fn cosine_similarity_test() {
// 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))
}
pub fn chebyshev_distance_test() {
// Empty lists returns an error
metrics.chebyshev_distance([], [])
metrics.chebyshev_distance([], [], option.None)
|> should.be_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()
// 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()
// 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()
// 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))
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))
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))
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))
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))
}
@ -367,3 +374,99 @@ 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()
}
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()
}