mirror of
https://github.com/sigmasternchen/gleam-community-maths
synced 2025-03-15 07:59:01 +00:00
Add canberra and bary-curtis distance
This commit is contained in:
parent
d9c642062a
commit
b7f7b29e48
4 changed files with 432 additions and 138 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue