@@ -231,34 +276,24 @@ 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
+ Ok(_) -> {
+ case p <. 1.0 {
True ->
- case p <. 1.0 {
- True ->
- "Invalid input argument: p < 1. Valid input is p >= 1."
- |> Error
- False ->
- list.zip(xarr, yarr)
- |> list.map(fn(tuple: #(Float, Float)) -> Float {
- pair.first(tuple) -. pair.second(tuple)
- })
- |> norm(p)
- |> Ok
- }
+ "Invalid input argument: p < 1. Valid input is p >= 1."
+ |> Error
+ False ->
+ list.zip(xarr, yarr)
+ |> list.map(fn(tuple: #(Float, Float)) -> Float {
+ pair.first(tuple) -. pair.second(tuple)
+ })
+ |> norm(p)
+ |> Ok
}
}
}
@@ -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)
}
///
@@ -364,32 +400,21 @@ 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 -> {
- 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)
- }
- }
+ Ok(_) -> {
+ 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
_ ->
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,34 +994,23 @@ 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 -> {
- 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
- }
- }
+ 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(
}
}
}
+
+///
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_community/maths/metrics
+///
+/// pub fn example () {
+/// }
+///
+///
+///
+///
+///
+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
+}
+
+///
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_community/maths/metrics
+///
+/// pub fn example () {
+/// }
+///
+///
+///
+///
+///
+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
+ }
+ }
+ }
+ }
+}
diff --git a/test/gleam_community/maths/arithmetics_test.gleam b/test/gleam_community/maths/arithmetics_test.gleam
index aa694b7..7ec5ad5 100644
--- a/test/gleam_community/maths/arithmetics_test.gleam
+++ b/test/gleam_community/maths/arithmetics_test.gleam
@@ -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() {
diff --git a/test/gleam_community/maths/metrics_test.gleam b/test/gleam_community/maths/metrics_test.gleam
index e5888a0..3ccf758 100644
--- a/test/gleam_community/maths/metrics_test.gleam
+++ b/test/gleam_community/maths/metrics_test.gleam
@@ -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()
+}