diff --git a/src/gleam_community/maths/arithmetics.gleam b/src/gleam_community/maths/arithmetics.gleam index a205116..dfa3c8d 100644 --- a/src/gleam_community/maths/arithmetics.gleam +++ b/src/gleam_community/maths/arithmetics.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -58,8 +58,8 @@ import gleam_community/maths/piecewise /// /// /// The function calculates the greatest common divisor of two integers -/// $$x, y \in \mathbb{Z}$$. The greatest common divisor is the largest positive -/// integer that is divisible by both $$x$$ and $$y$$. +/// \\(x, y \in \mathbb{Z}\\). The greatest common divisor is the largest positive +/// integer that is divisible by both \\(x\\) and \\(y\\). /// ///
/// Example: @@ -108,15 +108,15 @@ fn do_gcd(x: Int, y: Int) -> Int { /// /// /// -/// Given two integers, $$x$$ (dividend) and $$y$$ (divisor), the Euclidean modulo -/// of $$x$$ by $$y$$, denoted as $$x \mod y$$, is the remainder $$r$$ of the -/// division of $$x$$ by $$y$$, such that: +/// Given two integers, \\(x\\) (dividend) and \\(y\\) (divisor), the Euclidean modulo +/// of \\(x\\) by \\(y\\), denoted as \\(x \mod y\\), is the remainder \\(r\\) of the +/// division of \\(x\\) by \\(y\\), such that: /// /// \\[ /// x = q \cdot y + r \quad \text{and} \quad 0 \leq r < |y|, /// \\] /// -/// where $$q$$ is an integer that represents the quotient of the division. +/// where \\(q\\) is an integer that represents the quotient of the division. /// /// The Euclidean modulo function of two numbers, is the remainder operation most /// commonly utilized in mathematics. This differs from the standard truncating @@ -169,8 +169,8 @@ pub fn int_euclidean_modulo(x: Int, y: Int) -> Int { /// /// /// The function calculates the least common multiple of two integers -/// $$x, y \in \mathbb{Z}$$. The least common multiple is the smallest positive -/// integer that has both $$x$$ and $$y$$ as factors. +/// \\(x, y \in \mathbb{Z}\\). The least common multiple is the smallest positive +/// integer that has both \\(x\\) and \\(y\\) as factors. /// ///
/// Example: @@ -305,9 +305,9 @@ pub fn proper_divisors(n: Int) -> List(Int) { /// \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$$, while the $$w_i \in \mathbb{R}$$ -/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\) is +/// the value in the input list indexed by \\(i\\), while the \\(w_i \in \mathbb{R}\\) +/// are corresponding weights (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -362,8 +362,8 @@ pub fn float_sum(arr: List(Float), weights: option.Option(List(Float))) -> Float /// \sum_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is -/// the value in the input list indexed by $$i$$. +/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is +/// the value in the input list indexed by \\(i\\). /// ///
/// Example: @@ -411,9 +411,9 @@ pub fn int_sum(arr: List(Int)) -> Int { /// \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$$, while the $$w_i \in \mathbb{R}$$ -/// are corresponding weights ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\) is +/// the value in the input list indexed by \\(i\\), while the \\(w_i \in \mathbb{R}\\) +/// are corresponding weights (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -486,8 +486,8 @@ pub fn float_product( /// \prod_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is -/// the value in the input list indexed by $$i$$. +/// In the formula, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) is +/// the value in the input list indexed by \\(i\\). /// ///
/// Example: @@ -535,10 +535,10 @@ pub fn int_product(arr: List(Int)) -> Int { /// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n /// \\] /// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ -/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ -/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the -/// sum of the $$1$$ to $$j$$ first elements in the given list. +/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative sum of \\(n\\) +/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{R}\\) +/// is the value in the input list indexed by \\(i\\). The value \\(v_j\\) is thus the +/// sum of the \\(1\\) to \\(j\\) first elements in the given list. /// ///
/// Example: @@ -585,10 +585,10 @@ pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) { /// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n /// \\] /// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ -/// elements. That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ -/// is the value in the input list indexed by $$i$$. The value $$v_j$$ is thus the -/// sum of the $$1$$ to $$j$$ first elements in the given list. +/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative sum of \\(n\\) +/// elements. That is, \\(n\\) is the length of the list and \\(x_i \in \mathbb{Z}\\) +/// is the value in the input list indexed by \\(i\\). The value \\(v_j\\) is thus the +/// sum of the \\(1\\) to \\(j\\) first elements in the given list. /// ///
/// Example: @@ -635,10 +635,10 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) { /// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n /// \\] /// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of -/// $$n$$ elements. That is, $$n$$ is the length of the list and -/// $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. The -/// value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the +/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of +/// \\(n\\) elements. That is, \\(n\\) is the length of the list and +/// \\(x_i \in \mathbb{R}\\) is the value in the input list indexed by \\(i\\). The +/// value \\(v_j\\) is thus the sum of the \\(1\\) to \\(j\\) first elements in the /// given list. /// ///
@@ -687,10 +687,10 @@ pub fn float_cumulative_product(arr: List(Float)) -> List(Float) { /// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n /// \\] /// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of -/// $$n$$ elements. That is, $$n$$ is the length of the list and -/// $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. The -/// value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the +/// In the formula, \\(v_j\\) is the \\(j\\)'th element in the cumulative product of +/// \\(n\\) elements. That is, \\(n\\) is the length of the list and +/// \\(x_i \in \mathbb{Z}\\) is the value in the input list indexed by \\(i\\). The +/// value \\(v_j\\) is thus the product of the \\(1\\) to \\(j\\) first elements in the /// given list. /// ///
diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index e60555f..b026c4d 100644 --- a/src/gleam_community/maths/combinatorics.gleam +++ b/src/gleam_community/maths/combinatorics.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -24,7 +24,7 @@ //// --- //// //// Combinatorics: A module that offers mathematical functions related to counting, arrangements, -//// and combinations. +//// and permutations/combinations. //// //// * **Combinatorial functions** //// * [`combination`](#combination) @@ -53,14 +53,14 @@ pub type CombinatoricsMode { /// /// /// -/// A combinatorial function for computing the number of $$k$$-combinations of $$n$$ elements: +/// A combinatorial function for computing the number of \\(k\\)-combinations of \\(n\\) elements. /// /// **Without Repetitions:** /// /// \\[ /// C(n, k) = \binom{n}{k} = \frac{n!}{k! (n-k)!} /// \\] -/// Also known as "$$n$$ choose $$k$$" or the binomial coefficient. +/// Also known as "\\(n\\) choose \\(k\\)" or the binomial coefficient. /// /// **With Repetitions:** /// @@ -73,22 +73,24 @@ pub type CombinatoricsMode { /// ///
/// Details -/// A $$k$$-combination is a sequence of $$k$$ elements selected from $$n$$ elements where +/// +/// A \\(k\\)-combination is a sequence of \\(k\\) elements selected from \\(n\\) elements where /// the order of selection does not matter. For example, consider selecting 2 elements from a list /// of 3 elements: `["A", "B", "C"]`: /// -/// - For $$k$$-combinations (without repetitions), where order does not matter, the possible +/// - For \\(k\\)-combinations (without repetitions), where order does not matter, the possible /// selections are: -/// - ["A", "B"] -/// - ["A", "C"] -/// - ["B", "C"] +/// - `["A", "B"]` +/// - `["A", "C"]` +/// - `["B", "C"]` /// -/// - For $$k$$-combinations (with repetitions), where order does not matter but elements can +/// - For \\(k\\)-combinations (with repetitions), where order does not matter but elements can /// repeat, the possible selections are: -/// - ["A", "A"], ["A", "B"], ["A", "C"] -/// - ["B", "B"], ["B", "C"], ["C", "C"] +/// - `["A", "A"], ["A", "B"], ["A", "C"]` +/// - `["B", "B"], ["B", "C"], ["C", "C"]` /// -/// - On the contrary, for $$k$$-permutations, the order matters, so the possible selections are: +/// - On the contrary, for \\(k\\)-permutations (without repetitions), the order matters, so the +/// possible selections are: /// - `["A", "B"], ["B", "A"]` /// - `["A", "C"], ["C", "A"]` /// - `["B", "C"], ["C", "B"]` @@ -96,25 +98,26 @@ pub type CombinatoricsMode { ///
/// Example: /// +/// import gleam/option /// import gleeunit/should /// import gleam_community/maths/combinatorics /// /// pub fn example() { /// // Invalid input gives an error -/// // Error on: n = -1 < 0 -/// combinatorics.combination(-1, 1) +/// combinatorics.combination(-1, 1, option.None) /// |> should.be_error() -/// -/// // Valid input returns a result -/// combinatorics.combination(4, 0) +/// +/// // Valid input: n = 4 and k = 0 +/// combinatorics.combination(4, 0, option.Some(combinatorics.WithoutRepetitions)) /// |> should.equal(Ok(1)) -/// -/// combinatorics.combination(4, 4) +/// +/// // Valid input: k = n (n = 4, k = 4) +/// combinatorics.combination(4, 4, option.Some(combinatorics.WithoutRepetitions)) /// |> should.equal(Ok(1)) -/// -/// combinatorics.combination(4, 2) -/// |> should.equal(Ok(6)) -/// } +/// +/// // Valid input: combinations with repetition (n = 2, k = 3) +/// combinatorics.combination(2, 3, option.Some(combinatorics.WithRepetitions)) +/// |> should.equal(Ok(4)) ///
/// ///
@@ -128,44 +131,38 @@ pub fn combination( k: Int, mode: option.Option(CombinatoricsMode), ) -> Result(Int, String) { - case mode { - option.Some(WithRepetitions) -> combination_with_repetitions(n, k) - _ -> combination_without_repetitions(n, k) - } -} - -fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, String) { - case n < 0 || k < 0 || k > n { - True -> "Invalid input: Ensure n >= 0, k >= 0, and k <= n." |> Error - False -> - case k == 0 || k == n { - True -> 1 |> Ok - False -> { - let min = case k < n - k { - True -> k - False -> n - k - } - list.range(1, min) - |> list.fold(1, fn(acc: Int, x: Int) -> Int { - acc * { n + 1 - x } / x - }) - |> Ok - } + case n, k { + _, _ if n < 0 -> + "Invalid input argument: n < 0. Valid input is n >= 0." |> Error + _, _ if k < 0 -> + "Invalid input argument: k < 0. Valid input is k >= 0." |> Error + _, _ -> { + case mode { + option.Some(WithRepetitions) -> combination_with_repetitions(n, k) + _ -> combination_without_repetitions(n, k) } + } } } fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, String) { - case n < 0 { - True -> "Invalid input argument: n < 0. Valid input is n >= 0 " |> Error - False -> { - case k < 0 { - True -> "Invalid input argument: k < 0. Valid input is k >= 0 " |> Error - False -> { - { n + k - 1 } - |> combination_without_repetitions(k) - } + { n + k - 1 } + |> combination_without_repetitions(k) +} + +fn combination_without_repetitions(n: Int, k: Int) -> Result(Int, String) { + case n, k { + _, _ if k == 0 || k == n -> { + 1 |> Ok + } + _, _ -> { + let min = case k < n - k { + True -> k + False -> n - k } + list.range(1, min) + |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * { n + 1 - x } / x }) + |> Ok } } } @@ -176,8 +173,8 @@ fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, String) { /// ///
/// -/// A combinatorial function for computing the total number of combinations of $$n$$ -/// elements, that is $$n!$$. +/// A combinatorial function for computing the total number of combinations of \\(n\\) +/// elements, that is \\(n!\\). /// ///
/// Example: @@ -190,21 +187,12 @@ fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, String) { /// combinatorics.factorial(-1) /// |> should.be_error() /// -/// // Valid input returns a result +/// // Valid input returns a result (n = 0) /// combinatorics.factorial(0) /// |> should.equal(Ok(1)) -/// -/// combinatorics.factorial(1) -/// |> should.equal(Ok(1)) -/// -/// combinatorics.factorial(2) -/// |> should.equal(Ok(2)) -/// +/// /// combinatorics.factorial(3) /// |> should.equal(Ok(6)) -/// -/// combinatorics.factorial(4) -/// |> should.equal(Ok(24)) /// } ///
/// @@ -215,23 +203,20 @@ fn combination_with_repetitions(n: Int, k: Int) -> Result(Int, String) { /// /// pub fn factorial(n) -> Result(Int, String) { - case n < 0 { - True -> + case n { + _ if n < 0 -> "Invalid input argument: n < 0. Valid input is n >= 0." |> Error - False -> - case n { - 0 -> - 1 - |> Ok - 1 -> - 1 - |> Ok - _ -> - list.range(1, n) - |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * x }) - |> Ok - } + 0 -> + 1 + |> Ok + 1 -> + 1 + |> Ok + _ -> + list.range(1, n) + |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * x }) + |> Ok } } @@ -241,8 +226,7 @@ pub fn factorial(n) -> Result(Int, String) { /// /// /// -/// A combinatorial function for computing the number of $$k$$-permutations (without and without -/// repetitions) of $$n$$ elements. +/// A combinatorial function for computing the number of \\(k\\)-permutations. /// /// **Without** repetitions: /// @@ -256,51 +240,52 @@ pub fn factorial(n) -> Result(Int, String) { /// P^*(n, k) = n^k /// \\] /// +/// The implementation uses an efficient iterative multiplicative formula for computing the result. +/// ///
/// Details -/// A $$k$$-permutation (without repetitions) is a sequence of $$k$$ elements selected from $$n$$ -/// elements where the order of selection matters. For example, consider selecting 2 elements from -/// a list of 3 elements: `["A", "B", "C"]`: /// -/// - For $$k$$-permutations (without repetitions), the order matters, so the possible selections +/// A \\(k\\)-permutation (without repetitions) is a sequence of \\(k\\) elements selected from \ +/// \\(n\\) elements where the order of selection matters. For example, consider selecting 2 +/// elements from a list of 3 elements: `["A", "B", "C"]`: +/// +/// - For \\(k\\)-permutations (without repetitions), the order matters, so the possible selections /// are: /// - `["A", "B"], ["B", "A"]` /// - `["A", "C"], ["C", "A"]` /// - `["B", "C"], ["C", "B"]` /// -/// - For $$k$$-permutations (with repetitions), the order also matters, but we have repeated +/// - For \\(k\\)-permutations (with repetitions), the order also matters, but we have repeated /// selections: -/// - ["A", "A"], ["A", "B"], ["A", "C"] -/// - ["B", "A"], ["B", "B"], ["B", "C"] -/// - ["C", "A"], ["C", "B"], ["C", "C"] +/// - `["A", "A"], ["A", "B"], ["A", "C"]` +/// - `["B", "A"], ["B", "B"], ["B", "C"]` +/// - `["C", "A"], ["C", "B"], ["C", "C"]` /// -/// - On the contrary, for $$k$$-combinations, where order does not matter, the possible selections -/// are: -/// - ["A", "B"] -/// - ["A", "C"] -/// - ["B", "C"] +/// - On the contrary, for \\(k\\)-combinations (without repetitions), where order does not matter, +/// the possible selections are: +/// - `["A", "B"]` +/// - `["A", "C"]` +/// - `["B", "C"]` ///
/// ///
/// Example: -/// +/// +/// import gleam/option /// import gleeunit/should /// import gleam_community/maths/combinatorics /// /// pub fn example() { /// // Invalid input gives an error -/// // Error on: n = -1 < 0 -/// combinatorics.permutation(-1, 1) +/// combinatorics.permutation(-1, 1, option.None) /// |> should.be_error() /// -/// // Valid input returns a result -/// combinatorics.permutation(4, 0) +/// // Valid input returns a result (n = 4, k = 0) +/// combinatorics.permutation(4, 0, option.Some(combinatorics.WithoutRepetitions)) /// |> should.equal(Ok(1)) /// -/// combinatorics.permutation(4, 4) -/// |> should.equal(Ok(1)) -/// -/// combinatorics.permutation(4, 2) +/// // Valid input returns the correct number of permutations (n = 4, k = 2) +/// combinatorics.permutation(4, 2, option.Some(combinatorics.WithoutRepetitions)) /// |> should.equal(Ok(12)) /// } ///
@@ -316,54 +301,43 @@ pub fn permutation( k: Int, mode: option.Option(CombinatoricsMode), ) -> Result(Int, String) { - case mode { - option.Some(WithRepetitions) -> permutation_with_repetitions(n, k) - _ -> permutation_without_repetitions(n, k) + case n, k { + _, _ if n < 0 -> + "Invalid input argument: n < 0. Valid input is n >= 0." |> Error + _, _ if k < 0 -> + "Invalid input argument: k < 0. Valid input is k >= 0." |> Error + _, _ -> { + case mode { + option.Some(WithRepetitions) -> permutation_with_repetitions(n, k) + _ -> permutation_without_repetitions(n, k) + } + } } } fn permutation_without_repetitions(n: Int, k: Int) -> Result(Int, String) { - case n < 0 { - True -> - "Invalid input argument: n < 0. Valid input is n >= 0." - |> Error - False -> - case k < 0 || k > n { - True -> - 0 - |> Ok - False -> - case k == 0 { - True -> 1 |> Ok - False -> - list.range(0, k - 1) - |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * { n - x } }) - |> Ok - } - } + case n, k { + _, _ if k < 0 || k > n -> { + 0 |> Ok + } + _, _ if k == 0 -> { + 1 |> Ok + } + _, _ -> + list.range(0, k - 1) + |> list.fold(1, fn(acc: Int, x: Int) -> Int { acc * { n - x } }) + |> Ok } } fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) { - case n < 0 { - True -> - "Invalid input argument: n < 0. Valid input is n >= 0." - |> Error - False -> - case k < 0 { - True -> - "Invalid input argument: k < 0. Valid input is k >= 0." - |> Error - False -> { - let n_float = conversion.int_to_float(n) - let k_float = conversion.int_to_float(k) - let assert Ok(result) = elementary.power(n_float, k_float) - result - |> conversion.float_to_int() - |> Ok - } - } - } + let n_float = conversion.int_to_float(n) + let k_float = conversion.int_to_float(k) + // 'n' ank 'k' are positive integers, so no errors here... + let assert Ok(result) = elementary.power(n_float, k_float) + result + |> conversion.float_to_int() + |> Ok } ///
@@ -372,18 +346,33 @@ fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) { /// ///
/// -/// Generate all $$k$$-combinations based on a given list. +/// Generates all possible combinations of \\(k\\) elements selected from a given list of size +/// \\(n\\). +/// +/// The function can handle cases with and without repetitions +/// (see more details [here](#combination)). Also, note that repeated elements are treated as +/// distinct. /// ///
/// Example: /// -/// import gleeunit/should /// import gleam/set +/// import gleam/option +/// import gleam/iterator +/// import gleeunit/should /// import gleam_community/maths/combinatorics /// /// pub fn example () { -/// let assert Ok(result) = combinatorics.list_combination([1, 2, 3, 4], 3) +/// // Generate all 3-combinations without repetition +/// let assert Ok(result) = +/// combinatorics.list_combination( +/// [1, 2, 3, 4], +/// 3, +/// option.Some(combinatorics.WithoutRepetitions), +/// ) +/// /// result +/// |> iterator.to_list() /// |> set.from_list() /// |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]])) /// } @@ -398,22 +387,43 @@ fn permutation_with_repetitions(n: Int, k: Int) -> Result(Int, String) { pub fn list_combination( arr: List(a), k: Int, + mode: option.Option(CombinatoricsMode), ) -> Result(iterator.Iterator(List(a)), String) { - case k < 0 { - True -> Error("Invalid input argument: k < 0. Valid input is k >= 0.") - False -> { - case k > list.length(arr) { - True -> - Error( - "Invalid input argument: k > length(arr). Valid input is 0 <= k <= length(arr).", - ) - False -> Ok(do_list_combination(iterator.from_list(arr), k, [])) + case k { + _ if k < 0 -> + "Invalid input argument: k < 0. Valid input is k >= 0." + |> Error + _ -> + case mode { + option.Some(WithRepetitions) -> + list_combination_with_repetitions(arr, k) + _ -> list_combination_without_repetitions(arr, k) } + } +} + +fn list_combination_without_repetitions( + arr: List(a), + k: Int, +) -> Result(iterator.Iterator(List(a)), String) { + case k, list.length(arr) { + _, arr_length if k > arr_length -> { + "Invalid input argument: k > length(arr). Valid input is 0 <= k <= length(arr)." + |> Error + } + // Special case: When k = n, then the entire list is the only valid combination + _, arr_length if k == arr_length -> { + iterator.single(arr) |> Ok + } + _, _ -> { + Ok( + do_list_combination_without_repetitions(iterator.from_list(arr), k, []), + ) } } } -fn do_list_combination( +fn do_list_combination_without_repetitions( arr: iterator.Iterator(a), k: Int, prefix: List(a), @@ -424,8 +434,36 @@ fn do_list_combination( case arr |> iterator.step { iterator.Done -> iterator.empty() iterator.Next(x, xs) -> { - let with_x = do_list_combination(xs, k - 1, [x, ..prefix]) - let without_x = do_list_combination(xs, k, prefix) + let with_x = + do_list_combination_without_repetitions(xs, k - 1, [x, ..prefix]) + let without_x = do_list_combination_without_repetitions(xs, k, prefix) + iterator.concat([with_x, without_x]) + } + } + } +} + +fn list_combination_with_repetitions( + arr: List(a), + k: Int, +) -> Result(iterator.Iterator(List(a)), String) { + Ok(do_list_combination_with_repetitions(iterator.from_list(arr), k, [])) +} + +fn do_list_combination_with_repetitions( + arr: iterator.Iterator(a), + k: Int, + prefix: List(a), +) -> iterator.Iterator(List(a)) { + case k { + 0 -> iterator.single(list.reverse(prefix)) + _ -> + case arr |> iterator.step { + iterator.Done -> iterator.empty() + iterator.Next(x, xs) -> { + let with_x = + do_list_combination_with_repetitions(arr, k - 1, [x, ..prefix]) + let without_x = do_list_combination_with_repetitions(xs, k, prefix) iterator.concat([with_x, without_x]) } } @@ -438,37 +476,44 @@ fn do_list_combination( /// /// /// -/// Generate all permutations of a given list. +/// Generates all possible permutations of \\(k\\) elements selected from a given list of size +/// \\(n\\). /// -/// Repeated elements are treated as distinct for the -/// purpose of permutations, so two identical elements -/// for example will appear "both ways round". This -/// means lists with repeated elements return the same -/// number of permutations as ones without. +/// The function can handle cases with and without repetitions +/// (see more details [here](#permutation)). Also, note that repeated elements are treated as +/// distinct. /// ///
/// Example: /// -/// import gleeunit/should /// import gleam/set +/// import gleam/option +/// import gleam/iterator +/// import gleeunit/should /// import gleam_community/maths/combinatorics /// /// pub fn example () { -/// [1, 2, 3] -/// |> combinatorics.list_permutation() +/// // Generate all 3-permutations without repetition +/// let assert Ok(result) = +/// combinatorics.list_permutation( +/// [1, 2, 3], +/// 3, +/// option.Some(combinatorics.WithoutRepetitions), +/// ) +/// +/// result +/// |> iterator.to_list() /// |> set.from_list() -/// |> should.equal(set.from_list([ -/// [1, 2, 3], -/// [2, 1, 3], -/// [3, 1, 2], -/// [1, 3, 2], -/// [2, 3, 1], -/// [3, 2, 1], -/// ])) -/// -/// [1.0, 1.0] -/// |> combinatorics.list_permutation() -/// |> should.equal([[1.0, 1.0], [1.0, 1.0]]) +/// |> should.equal( +/// set.from_list([ +/// [1, 2, 3], +/// [2, 1, 3], +/// [3, 1, 2], +/// [1, 3, 2], +/// [2, 3, 1], +/// [3, 2, 1], +/// ]), +/// ) /// } ///
/// @@ -478,35 +523,96 @@ fn do_list_combination( /// /// /// -pub fn list_permutation(arr: List(a)) -> iterator.Iterator(List(a)) { - case arr { - [] -> iterator.single([]) +/// +pub fn list_permutation( + arr: List(a), + k: Int, + mode: option.Option(CombinatoricsMode), +) -> Result(iterator.Iterator(List(a)), String) { + case k { + _ if k < 0 -> + "Invalid input argument: k < 0. Valid input is k >= 0." + |> Error _ -> - iterator.from_list(arr) - // Iterate over each element in the list 'arr' to generate permutations for each possible - // starting element 'x'. - |> iterator.flat_map(fn(x) { - // For each element 'x', we remove it from the list. This will gives us the remaining list - // that contains all elements except 'x'. - let remaining = remove_first(arr, x) - // Recursively call 'list_permutation' on the remaining list to generate all permutations - // of the smaller list. - let permutations = list_permutation(remaining) - // For each permutation generated by the recursive call, we prepend the element 'x' back to - // the front of the permutation. - iterator.map(permutations, fn(permutation) { [x, ..permutation] }) + case mode { + option.Some(WithRepetitions) -> + list_permutation_with_repetitions(arr, k) + _ -> list_permutation_without_repetitions(arr, k) + } + } +} + +fn remove_first_by_index( + arr: iterator.Iterator(#(Int, a)), + index_to_remove: Int, +) -> iterator.Iterator(#(Int, a)) { + iterator.flat_map(arr, fn(arg) { + let #(index, element) = arg + case index == index_to_remove { + True -> iterator.empty() + False -> iterator.single(#(index, element)) + } + }) +} + +fn list_permutation_without_repetitions( + arr: List(a), + k: Int, +) -> Result(iterator.Iterator(List(a)), String) { + case k, list.length(arr) { + _, arr_length if k > arr_length -> { + "Invalid input argument: k > length(arr). Valid input is 0 <= k <= length(arr)." + |> Error + } + _, _ -> { + let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) }) + Ok(do_list_permutation_without_repetitions( + iterator.from_list(indexed_arr), + k, + )) + } + } +} + +fn do_list_permutation_without_repetitions( + arr: iterator.Iterator(#(Int, a)), + k: Int, +) -> iterator.Iterator(List(a)) { + case k { + 0 -> iterator.single([]) + _ -> + iterator.flat_map(arr, fn(arg) { + let #(index, element) = arg + let remaining = remove_first_by_index(arr, index) + let permutations = + do_list_permutation_without_repetitions(remaining, k - 1) + iterator.map(permutations, fn(permutation) { [element, ..permutation] }) }) } } -fn remove_first(list: List(a), x: a) -> List(a) { - case list { - [] -> [] - [head, ..tail] -> - case head == x { - True -> tail - False -> [head, ..remove_first(tail, x)] - } +fn list_permutation_with_repetitions( + arr: List(a), + k: Int, +) -> Result(iterator.Iterator(List(a)), String) { + let indexed_arr = list.index_map(arr, fn(x, i) { #(i, x) }) + Ok(do_list_permutation_with_repetitions(indexed_arr, k)) +} + +fn do_list_permutation_with_repetitions( + arr: List(#(Int, a)), + k: Int, +) -> iterator.Iterator(List(a)) { + case k { + 0 -> iterator.single([]) + _ -> + iterator.flat_map(arr |> iterator.from_list, fn(arg) { + let #(_, element) = arg + // Allow the same element (by index) to be reused in future recursive calls + let permutations = do_list_permutation_with_repetitions(arr, k - 1) + // Prepend the current element to each generated permutation + iterator.map(permutations, fn(permutation) { [element, ..permutation] }) + }) } } @@ -521,18 +627,22 @@ fn remove_first(list: List(a), x: a) -> List(a) { ///
/// Example: /// +/// import gleam/set /// import gleeunit/should -/// import gleam/list /// import gleam_community/maths/combinatorics /// /// pub fn example () { -/// [] -/// |> combinatorics.cartesian_product([]) -/// |> should.equal([]) +/// // Cartesian product of two empty sets +/// set.from_list([]) +/// |> combinatorics.cartesian_product(set.from_list([])) +/// |> should.equal(set.from_list([])) /// -/// [1.0, 10.0] -/// |> combinatorics.cartesian_product([1.0, 2.0]) -/// |> should.equal([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) +/// // Cartesian product of two sets with numeric values +/// set.from_list([1.0, 10.0]) +/// |> combinatorics.cartesian_product(set.from_list([1.0, 2.0])) +/// |> should.equal( +/// set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]), +/// ) /// } ///
/// @@ -542,13 +652,7 @@ fn remove_first(list: List(a), x: a) -> List(a) { /// /// /// -pub fn cartesian_product(xarr: List(a), yarr: List(a)) -> List(#(a, a)) { - let xset: set.Set(a) = - xarr - |> set.from_list() - let yset: set.Set(a) = - yarr - |> set.from_list() +pub fn cartesian_product(xset: set.Set(a), yset: set.Set(a)) -> set.Set(#(a, a)) { xset |> set.fold( set.new(), @@ -562,5 +666,4 @@ pub fn cartesian_product(xarr: List(a), yarr: List(a)) -> List(#(a, a)) { ) }, ) - |> set.to_list() } diff --git a/src/gleam_community/maths/conversion.gleam b/src/gleam_community/maths/conversion.gleam index 9ac9036..1355938 100644 --- a/src/gleam_community/maths/conversion.gleam +++ b/src/gleam_community/maths/conversion.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -76,7 +76,8 @@ pub fn int_to_float(x: Int) -> Float { /// /// /// The function returns the integral part of a given floating point value. -/// That is, everything after the decimal point of a given floating point value is discarded and only the integer value before the decimal point is returned. +/// That is, everything after the decimal point of a given floating point value is discarded +/// and only the integer value before the decimal point is returned. /// ///
/// Example @@ -119,7 +120,7 @@ fn do_to_int(a: Float) -> Int /// /// /// Convert a value in degrees to a value measured in radians. -/// That is, $$1 \text{ degrees } = \frac{\pi}{180} \text{ radians }$$. +/// That is, \\(1 \text{ degrees } = \frac{\pi}{180} \text{ radians }\\). /// ///
/// Example @@ -151,7 +152,7 @@ pub fn degrees_to_radians(x: Float) -> Float { /// /// /// Convert a value in degrees to a value measured in radians. -/// That is, $$1 \text{ radians } = \frac{180}{\pi} \text{ degrees }$$. +/// That is, \\(1 \text{ radians } = \frac{180}{\pi} \text{ degrees }\\). /// ///
/// Example diff --git a/src/gleam_community/maths/elementary.gleam b/src/gleam_community/maths/elementary.gleam index 0c1a3ba..25e6b47 100644 --- a/src/gleam_community/maths/elementary.gleam +++ b/src/gleam_community/maths/elementary.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -54,10 +54,8 @@ //// * [`tau`](#tau) //// * [`e`](#e) //// -//// import gleam/int -import gleam/list import gleam/option ///
@@ -72,8 +70,8 @@ import gleam/option /// \forall x \in \[-1, 1\], \\; \cos^{-1}{(x)} = y \in \[0, \pi \] /// \\] /// -/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a -/// numeric value $$y$$ that lies in the range $$\[0, \pi \]$$ (an angle in radians). +/// The function takes a number \\(x\\) in its domain \\(\[-1, 1\]\\) as input and returns a +/// numeric value \\(y\\) that lies in the range \\(\[0, \pi \]\\) (an angle in radians). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -127,8 +125,8 @@ fn do_acos(a: Float) -> Float /// \forall x \in [1, +\infty\), \\; \cosh^{-1}{(x)} = y \in \[0, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\[1, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\[0, +\infty\)$$ (an angle in radians). +/// The function takes a number \\(x\\) in its domain \\(\[1, +\infty\)\\) as input and returns +/// a numeric value \\(y\\) that lies in the range \\(\[0, +\infty\)\\) (an angle in radians). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -179,9 +177,9 @@ fn do_acosh(a: Float) -> Float /// \forall x \in \[-1, 1\], \\; \sin^{-1}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a numeric -/// value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). -/// If the input value is outside the domain of the function an error is returned. +/// The function takes a number \\(x\\) in its domain \\(\[-1, 1\]\\) as input and returns a numeric +/// value \\(y\\) that lies in the range \\(\[-\frac{\pi}{2}, \frac{\pi}{2}\]\\) (an angle in +/// radians). If the input value is outside the domain of the function an error is returned. /// ///
/// Example @@ -234,8 +232,9 @@ fn do_asin(a: Float) -> Float /// \forall x \in \(-\infty, \infty\), \\; \sinh^{-1}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, +\infty\)$$ (an angle in radians). +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and +/// returns a numeric value \\(y\\) that lies in the range \\(\(-\infty, +\infty\)\\) (an angle in +/// radians). /// ///
/// Example @@ -272,11 +271,12 @@ fn do_asinh(a: Float) -> Float /// The inverse tangent function: /// /// \\[ -/// \forall x \in \(-\infty, \infty\), \\; \tan^{-1}{(x)} = y \in \[-\frac{\pi}{2}, \frac{\pi}{2}\] +/// \forall x \in \(-\infty, \infty\), \\; \tan^{-1}{(x)} = y \in \[-\frac{\pi}{2}, \frac{\pi}{2}\] /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input and +/// returns a numeric value \\(y\\) that lies in the range \\(\[-\frac{\pi}{2}, \frac{\pi}{2}\]\\) +/// (an angle in radians). /// ///
/// Example @@ -325,8 +325,8 @@ fn do_atan(a: Float) -> Float /// \\] /// /// The function returns the angle in radians from the x-axis to the line containing the -/// origin $$\(0, 0\)$$ and a point given as input with coordinates $$\(x, y\)$$. The numeric value -/// returned by $$\text{atan2}(y, x)$$ is in the range $$\[-\pi, \pi\]$$. +/// origin \\(\(0, 0\)\\) and a point given as input with coordinates \\(\(x, y\)\\). The numeric +/// value returned by \\(\text{atan2}(y, x)\\) is in the range \\(\[-\pi, \pi\]\\). /// ///
/// Example @@ -366,8 +366,8 @@ fn do_atan2(a: Float, b: Float) -> Float /// \forall x \in \(-1, 1\), \\; \tanh^{-1}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-1, 1\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$ (an angle in radians). +/// The function takes a number \\(x\\) in its domain \\(\(-1, 1\)\\) as input and returns +/// a numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\) (an angle in radians). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -421,8 +421,8 @@ fn do_atanh(a: Float) -> Float /// \forall x \in \(-\infty, +\infty\), \\; \cos{(x)} = y \in \[-1, 1\] /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) -/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in +/// radians) as input and returns a numeric value \\(y\\) that lies in the range \\(\[-1, 1\]\\). /// ///
/// Example @@ -465,9 +465,9 @@ fn do_cos(a: Float) -> Float /// \forall x \in \(-\infty, \infty\), \\; \cosh{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) -/// and returns a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is too large an overflow error might occur. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) as input (an angle +/// in radians) and returns a numeric value \\(y\\) that lies in the range +/// \\(\(-\infty, \infty\)\\). If the input value is too large an overflow error might occur. /// ///
/// Example @@ -507,8 +507,8 @@ fn do_cosh(a: Float) -> Float /// \forall x \in \(-\infty, +\infty\), \\; \sin{(x)} = y \in \[-1, 1\] /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) -/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) (an angle in +/// radians) as input and returns a numeric value \\(y\\) that lies in the range \\(\[-1, 1\]\\). /// ///
/// Example @@ -551,10 +551,9 @@ fn do_sin(a: Float) -> Float /// \forall x \in \(-\infty, +\infty\), \\; \sinh{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input -/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range -/// $$\(-\infty, +\infty\)$$. If the input value is too large an overflow error might -/// occur. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input +/// (an angle in radians) and returns a numeric value \\(y\\) that lies in the range +/// \\(\(-\infty, +\infty\)\\). If the input value is too large an overflow error might occur. /// ///
/// Example @@ -594,9 +593,9 @@ fn do_sinh(a: Float) -> Float /// \forall x \in \(-\infty, +\infty\), \\; \tan{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input -/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range -/// $$\(-\infty, +\infty\)$$. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, +\infty\)\\) as input +/// (an angle in radians) and returns a numeric value \\(y\\) that lies in the range +/// \\(\(-\infty, +\infty\)\\). /// ///
/// Example @@ -636,8 +635,8 @@ fn do_tan(a: Float) -> Float /// \forall x \in \(-\infty, \infty\), \\; \tanh{(x)} = y \in \[-1, 1\] /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) -/// and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// The function takes a number \\(x\\) in its domain \\(\(-\infty, \infty\)\\) as input (an angle +/// in radians) and returns a numeric value \\(y\\) that lies in the range \\(\[-1, 1\]\\). /// ///
/// Example @@ -683,9 +682,9 @@ fn do_tanh(a: Float) -> Float /// \forall x \in \(-\infty, \infty\), \\; e^{(x)} = y \in \(0, +\infty\) /// \\] /// -/// $$e \approx 2.71828\dots$$ is Eulers' number. +/// \\(e \approx 2.71828\dots\\) is Eulers' number. /// -/// Note: If the input value $$x$$ is too large an overflow error might occur. +/// Note: If the input value \\(x\\) is too large an overflow error might occur. /// ///
/// Example @@ -725,8 +724,8 @@ fn do_exponential(a: Float) -> Float /// \forall x \in \(0, \infty\), \\; \log_{e}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns +/// a numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -775,14 +774,14 @@ fn do_natural_logarithm(a: Float) -> Float /// ///
/// -/// The base $$b$$ logarithm function (computed through the "change of base" formula): +/// The base \\(b\\) logarithm function (computed through the "change of base" formula): /// /// \\[ /// \forall x \in \(0, \infty\) \textnormal{ and } b > 1, \\; \log_{b}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ and a base $$b > 1$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) and a base \\(b > 1\\) +/// as input and returns a numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -850,8 +849,8 @@ pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String) /// \forall x \in \(0, \infty), \\; \log_{2}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a +/// numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -905,8 +904,8 @@ fn do_logarithm_2(a: Float) -> Float /// \forall x \in \(0, \infty), \\; \log_{10}{(x)} = y \in \(-\infty, +\infty\) /// \\] /// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// The function takes a number \\(x\\) in its domain \\(\(0, \infty\)\\) as input and returns a +/// numeric value \\(y\\) that lies in the range \\(\(-\infty, \infty\)\\). /// If the input value is outside the domain of the function an error is returned. /// ///
@@ -960,14 +959,14 @@ fn do_logarithm_10(a: Float) -> Float /// /// /// -/// The exponentiation function: $$y = x^{a}$$. +/// The exponentiation function: \\(y = x^{a}\\). /// /// Note that the function is not defined if: -/// 1. The base is negative ($$x < 0$$) and the exponent is fractional -/// ($$a = \frac{n}{m}$$ is an irrreducible fraction). An error will be returned +/// 1. The base is negative (\\(x < 0\\)) and the exponent is fractional +/// (\\(a = \frac{n}{m}\\) is an irrreducible fraction). An error will be returned /// as an imaginary number will otherwise have to be returned. -/// 2. The base is zero ($$x = 0$$) and the exponent is negative ($$a < 0$$) then the -/// expression is equivalent to the exponent $$y$$ divided by $$0$$ and an +/// 2. The base is zero (\\(x = 0\\)) and the exponent is negative (\\(a < 0\\)) then the +/// expression is equivalent to the exponent \\(y\\) divided by \\(0\\) and an /// error will have to be returned as the expression is otherwise undefined. /// ///
@@ -1026,10 +1025,10 @@ fn do_ceiling(a: Float) -> Float /// /// /// -/// The square root function: $$y = \sqrt[2]{x} = x^{\frac{1}{2}}$$. +/// The square root function: \\(y = \sqrt[2]{x} = x^{\frac{1}{2}}\\). /// /// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned +/// 1. The input is negative (\\(x < 0\\)). An error will be returned /// as an imaginary number will otherwise have to be returned. /// ///
@@ -1078,10 +1077,10 @@ pub fn square_root(x: Float) -> Result(Float, String) { /// /// /// -/// The cube root function: $$y = \sqrt[3]{x} = x^{\frac{1}{3}}$$. +/// The cube root function: \\(y = \sqrt[3]{x} = x^{\frac{1}{3}}\\). /// /// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned +/// 1. The input is negative (\\(x < 0\\)). An error will be returned /// as an imaginary number will otherwise have to be returned. /// ///
@@ -1130,10 +1129,10 @@ pub fn cube_root(x: Float) -> Result(Float, String) { /// /// /// -/// The $$n$$'th root function: $$y = \sqrt[n]{x} = x^{\frac{1}{n}}$$. +/// The \\(n\\)'th root function: \\(y = \sqrt[n]{x} = x^{\frac{1}{n}}\\). /// /// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned +/// 1. The input is negative (\\(x < 0\\)). An error will be returned /// as an imaginary number will otherwise have to be returned. /// ///
@@ -1191,7 +1190,7 @@ pub fn nth_root(x: Float, n: Int) -> Result(Float, String) { /// /// /// -/// The mathematical constant pi: $$\pi \approx 3.1415\dots$$ +/// The mathematical constant pi: \\(\pi \approx 3.1415\dots\\) /// /// /// -/// The mathematical constant tau: $$\tau = 2 \cdot \pi \approx 6.283\dots$$ +/// The mathematical constant tau: \\(\tau = 2 \cdot \pi \approx 6.283\dots\\) /// /// /// -/// Euler's number $$e \approx 2.71828\dots$$. +/// Euler's number \\(e \approx 2.71828\dots\\). /// ///
/// Example diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam index 286044c..f4862eb 100644 --- a/src/gleam_community/maths/metrics.gleam +++ b/src/gleam_community/maths/metrics.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -126,15 +126,15 @@ fn validate_weights(warr: List(Float)) -> Result(Bool, String) { /// /// /// -/// Calculate the (weighted) $$p$$-norm of a list (representing a vector): +/// Calculate the (weighted) \\(p\\)-norm of a list (representing a vector): /// /// \\[ /// \left( \sum_{i=1}^n w_{i} \left|x_{i}\right|^{p} \right)^{\frac{1}{p}} /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in -/// the input list indexed by $$i$$, while $$w_i \in \mathbb{R}_{+}$$ is -/// a corresponding positive weight ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the list and \\(x_i\\) is the value in +/// the input list indexed by \\(i\\), while \\(w_i \in \mathbb{R}_{+}\\) is +/// a corresponding positive weight (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -246,10 +246,10 @@ pub fn norm( /// \sum_{i=1}^n w_{i} \left|x_i - y_i \right| /// \\] /// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the -/// values in the respective input lists indexed by $$i$$, while the -/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the +/// values in the respective input lists indexed by \\(i\\), while the +/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -306,13 +306,13 @@ pub fn manhattan_distance( /// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}} /// \\] /// -/// In the formula, $$p >= 1$$ is the order, $$n$$ is the length of the two lists -/// and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i$$. -/// The $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(p >= 1\\) is the order, \\(n\\) is the length of the two lists +/// and \\(x_i, y_i\\) are the values in the respective input lists indexed by \\(i\\). +/// The \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// /// The Minkowski distance is a generalization of both the Euclidean distance -/// ($$p=2$$) and the Manhattan distance ($$p = 1$$). +/// (\\(p=2\\)) and the Manhattan distance (\\(p = 1\\)). /// ///
/// Example: @@ -396,10 +396,10 @@ pub fn minkowski_distance( /// \left( \sum_{i=1}^n w_{i} \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}} /// \\] /// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the -/// values in the respective input lists indexed by $$i$$, while the -/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the +/// values in the respective input lists indexed by \\(i\\), while the +/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -455,8 +455,8 @@ pub fn euclidean_distance( /// \text{max}_{i=1}^n \left|x_i - y_i \right| /// \\] /// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the -/// values in the respective input lists indexed by $$i$$. +/// In the formula, \\(n\\) is the length of the two lists and \\(x_i, y_i\\) are the +/// values in the respective input lists indexed by \\(i\\). /// ///
/// Example: @@ -517,8 +517,8 @@ pub fn chebyshev_distance( /// \bar{x} = \frac{1}{n}\sum_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the sample size (the length of the list) and $$x_i$$ -/// is the sample point in the input list indexed by $$i$$. +/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\) +/// is the sample point in the input list indexed by \\(i\\). /// ///
/// Example: @@ -637,11 +637,11 @@ fn do_median( /// s^{2} = \frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x}) /// \\] /// -/// In the formula, $$n$$ is the sample size (the length of the list) and $$x_i$$ -/// is the sample point in the input list indexed by $$i$$. -/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta -/// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased -/// estimate of the sample variance. Setting $$d = 1$$ gives an unbiased estimate. +/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\) +/// is the sample point in the input list indexed by \\(i\\). +/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta +/// Degrees of Freedom", and is by default set to \\(d = 0\\), which gives a biased +/// estimate of the sample variance. Setting \\(d = 1\\) gives an unbiased estimate. /// ///
/// Example: @@ -713,11 +713,11 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { /// s = \left(\frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x})\right)^{\frac{1}{2}} /// \\] /// -/// In the formula, $$n$$ is the sample size (the length of the list) and $$x_i$$ -/// is the sample point in the input list indexed by $$i$$. -/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta -/// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased -/// estimate of the sample standard deviation. Setting $$d = 1$$ gives an unbiased +/// In the formula, \\(n\\) is the sample size (the length of the list) and \\(x_i\\) +/// is the sample point in the input list indexed by \\(i\\). +/// Furthermore, \\(\bar{x}\\) is the sample mean and \\(d\\) is the "Delta +/// Degrees of Freedom", and is by default set to \\(d = 0\\), which gives a biased +/// estimate of the sample standard deviation. Setting \\(d = 1\\) gives an unbiased /// estimate. /// ///
@@ -785,14 +785,14 @@ pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, String) /// /// where: /// -/// - $$X$$ and $$Y$$ are two sets being compared, -/// - $$|X \cap Y|$$ represents the size of the intersection of the two sets -/// - $$|X \cup Y|$$ denotes the size of the union of the two sets +/// - \\(X\\) and \\(Y\\) are two sets being compared, +/// - \\(|X \cap Y|\\) represents the size of the intersection of the two sets +/// - \\(|X \cup Y|\\) denotes the size of the union of the two sets /// /// The value of the Jaccard index ranges from 0 to 1, where 0 indicates that the /// two sets share no elements and 1 indicates that the sets are identical. The /// Jaccard index is a special case of the [Tversky index](#tversky_index) (with -/// $$\alpha=\beta=1$$). +/// \\(\alpha=\beta=1\\)). /// ///
/// Example: @@ -835,16 +835,16 @@ pub fn jaccard_index(xset: set.Set(a), yset: set.Set(a)) -> Float { /// \\] /// /// where: -/// - $$X$$ and $$Y$$ are two sets being compared -/// - $$|X \cap Y|$$ is the size of the intersection of the two sets (i.e., the +/// - \\(X\\) and \\(Y\\) are two sets being compared +/// - \\(|X \cap Y|\\) is the size of the intersection of the two sets (i.e., the /// number of elements common to both sets) -/// - $$|X|$$ and $$|Y|$$ are the sizes of the sets $$X$$ and $$Y$$, respectively +/// - \\(|X|\\) and \\(|Y|\\) are the sizes of the sets \\(X\\) and \\(Y\\), respectively /// /// The coefficient ranges from 0 to 1, where 0 indicates no similarity (the sets /// share no elements) and 1 indicates perfect similarity (the sets are identical). /// The higher the coefficient, the greater the similarity between the two sets. /// The Sørensen-Dice coefficient is a special case of the -/// [Tversky index](#tversky_index) (with $$\alpha=\beta=0.5$$). +/// [Tversky index](#tversky_index) (with \\(\alpha=\beta=0.5\\)). /// ///
/// Example: @@ -880,8 +880,8 @@ pub fn sorensen_dice_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float { /// /// /// The Tversky index is a generalization of the Jaccard index and Sørensen-Dice -/// coefficient, which adds flexibility through two parameters, $$\alpha$$ and -/// $$\beta$$, allowing for asymmetric similarity measures between sets. The +/// coefficient, which adds flexibility through two parameters, \\(\alpha\\) and +/// \\(\beta\\), allowing for asymmetric similarity measures between sets. The /// Tversky index is defined as: /// /// \\[ @@ -890,18 +890,18 @@ pub fn sorensen_dice_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float { /// /// where: /// -/// - $$X$$ and $$Y$$ are the sets being compared -/// - $$|X - Y|$$ and $$|Y - X|$$ are the sizes of the relative complements of -/// $$Y$$ in $$X$$ and $$X$$ in $$Y$$, respectively, -/// - $$\alpha$$ and $$\beta$$ are parameters that weigh the relative importance -/// of the elements unique to $$X$$ and $$Y$$ +/// - \\(X\\) and \\(Y\\) are the sets being compared +/// - \\(|X - Y|\\) and \\(|Y - X|\\) are the sizes of the relative complements of +/// \\(Y\\) in \\(X\\) and \\(X\\) in \\(Y\\), respectively, +/// - \\(\alpha\\) and \\(\beta\\) are parameters that weigh the relative importance +/// of the elements unique to \\(X\\) and \\(Y\\) /// -/// The Tversky index reduces to the Jaccard index when $$\alpha = \beta = 1$$ and -/// to the Sørensen-Dice coefficient when $$\alpha = \beta = 0.5$$. In general, the +/// The Tversky index reduces to the Jaccard index when \\(\alpha = \beta = 1\\) and +/// to the Sørensen-Dice coefficient when \\(\alpha = \beta = 0.5\\). In general, the /// Tversky index can take on any non-negative value, including 0. The index equals /// 0 when there is no intersection between the two sets, indicating no similarity. /// However, unlike similarity measures bounded strictly between 0 and 1, the -/// Tversky index does not have a strict upper limit of 1 when $$\alpha \neq \beta$$. +/// Tversky index does not have a strict upper limit of 1 when \\(\alpha \neq \beta\\). /// ///
/// Example: @@ -982,9 +982,9 @@ pub fn tversky_index( /// /// where: /// -/// - $$X$$ and $$Y$$ are the sets being compared -/// - $$|X \cap Y|$$ is the size of the intersection of the sets -/// - $$\min(|X|, |Y|)$$ is the size of the smaller set among $$X$$ and $$Y$$ +/// - \\(X\\) and \\(Y\\) are the sets being compared +/// - \\(|X \cap Y|\\) is the size of the intersection of the sets +/// - \\(\min(|X|, |Y|)\\) is the size of the smaller set among \\(X\\) and \\(Y\\) /// /// The coefficient ranges from 0 to 1, where 0 indicates no overlap and 1 /// indicates that the smaller set is a suyset of the larger set. This @@ -1043,10 +1043,10 @@ pub fn overlap_coefficient(xset: set.Set(a), yset: set.Set(a)) -> Float { /// \\; \in \\; \left[-1, 1\right] /// \\] /// -/// In the formula, $$n$$ is the length of the two lists and $$x_i$$, $$y_i$$ are -/// the values in the respective input lists indexed by $$i$$, while the -/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the two lists and \\(x_i\\), \\(y_i\\) are +/// the values in the respective input lists indexed by \\(i\\), while the +/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// /// The cosine similarity provides a value between -1 and 1, where 1 means the /// vectors are in the same direction, -1 means they are in exactly opposite @@ -1143,10 +1143,10 @@ pub fn cosine_similarity( /// {\left| x_i \right| + \left| y_i \right|} /// \\] /// -/// In the formula, $$n$$ is the length of the two lists, and $$x_i, y_i$$ are the -/// values in the respective input lists indexed by $$i$$, while the -/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the +/// values in the respective input lists indexed by \\(i\\), while the +/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// ///
/// Example: @@ -1231,12 +1231,12 @@ fn canberra_distance_helper(tuple: #(Float, Float)) -> Float { /// {\sum_{i=1}^n w_{i}\left| x_i + y_i \right|} /// \\] /// -/// In the formula, $$n$$ is the length of the two lists, and $$x_i, y_i$$ are the values -/// in the respective input lists indexed by $$i$$, while the -/// $$w_i \in \mathbb{R}_{+}$$ are corresponding positive weights -/// ($$w_i = 1.0\\;\forall i=1...n$$ by default). +/// In the formula, \\(n\\) is the length of the two lists, and \\(x_i, y_i\\) are the values +/// in the respective input lists indexed by \\(i\\), while the +/// \\(w_i \in \mathbb{R}_{+}\\) are corresponding positive weights +/// (\\(w_i = 1.0\\;\forall i=1...n\\) by default). /// -/// The Bray-Curtis distance is in the range $$[0, 1]$$ if all entries $$x_i, y_i$$ are +/// The Bray-Curtis distance is in the range \\([0, 1]\\) if all entries \\(x_i, y_i\\) are /// positive. /// ///
diff --git a/src/gleam_community/maths/piecewise.gleam b/src/gleam_community/maths/piecewise.gleam index 8496c0b..36a18dc 100644 --- a/src/gleam_community/maths/piecewise.gleam +++ b/src/gleam_community/maths/piecewise.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -66,24 +66,26 @@ import gleam_community/maths/elementary /// /// /// -/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. +/// The ceiling function rounds a given input value \\(x \in \mathbb{R}\\) to the nearest integer +/// value (at the specified digit) that is larger than or equal to the input \\(x\\). /// -/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundUp`. +/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) +/// with rounding mode `RoundUp`. /// ///
/// Details /// -/// For example, $$12.0654$$ is rounded to: -/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) +/// For example, \\(12.0654\\) is rounded to: +/// - \\(13.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.1\\) for 1 digit after the decimal point (`digits = 1`) +/// - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.066\\) for 3 digits after the decimal point (`digits = 3`) /// /// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// For example, $$12.0654$$ is rounded to: -/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) -/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) +/// For example, \\(12.0654\\) is rounded to: +/// - \\(20.0\\) for 1 digit places before the decimal point (`digit = -1`) +/// - \\(100.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(1000.0\\) for 3 digits before the decimal point (`digits = -3`) /// ///
/// @@ -122,23 +124,26 @@ pub fn ceiling(x: Float, digits: option.Option(Int)) -> Float { /// /// /// -/// The floor function rounds input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. +/// The floor function rounds input \\(x \in \mathbb{R}\\) to the nearest integer value (at the +/// specified digit) that is less than or equal to the input \\(x\\). /// -/// Note: The floor function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundDown`. +/// Note: The floor function is used as an alias for the rounding function [`round`](#round) +/// with rounding mode `RoundDown`. /// ///
/// Details /// -/// For example, $$12.0654$$ is rounded to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// For example, \\(12.0654\\) is rounded to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.0\\) for 1 digits after the decimal point (`digits = 1`) +/// - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// ///
/// @@ -177,23 +182,27 @@ pub fn floor(x: Float, digits: option.Option(Int)) -> Float { /// /// /// -/// The truncate function rounds a given input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. +/// The truncate function rounds a given input \\(x \in \mathbb{R}\\) to the nearest integer +/// value (at the specified digit) that is less than or equal to the absolute value of the +/// input \\(x\\). /// -/// Note: The truncate function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundToZero`. +/// Note: The truncate function is used as an alias for the rounding function [`round`](#round) +/// with rounding mode `RoundToZero`. /// ///
/// Details /// -/// For example, $$12.0654$$ is rounded to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// For example, \\(12.0654\\) is rounded to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.0\\) for 1 digits after the decimal point (`digits = 1`) +/// - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// ///
/// @@ -232,84 +241,103 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Float { /// /// /// -/// The function rounds a float to a specific number of digits (after the decimal place or before if negative) using a specified rounding mode. +/// The function rounds a float to a specific number of digits (after the decimal place or before +/// if negative) using a specified rounding mode. /// /// Valid rounding modes include: -/// - `RoundNearest` (default): The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded to the nearest even integer. -/// - `RoundTiesAway`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++ rounding behavior). -/// - `RoundTiesUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded towards $$+\infty$$ (Java/JavaScript rounding behaviour). -/// - `RoundToZero`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. An alias for this rounding mode is [`truncate`](#truncate). -/// - `RoundDown`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. An alias for this rounding mode is [`floor`](#floor). -/// - `RoundUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. An alias for this rounding mode is [`ceiling`](#ceiling). +/// - `RoundNearest` (default): The input \\(x\\) is rounded to the nearest integer value (at the +/// specified digit) with ties (fractional values of 0.5) being rounded to the nearest even +/// integer. +/// - `RoundTiesAway`: The input \\(x\\) is rounded to the nearest integer value (at the +/// specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++ +/// rounding behavior). +/// - `RoundTiesUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified +/// digit) with ties (fractional values of 0.5) being rounded towards \\(+\infty\\) +/// (Java/JavaScript rounding behaviour). +/// - `RoundToZero`: The input \\(x\\) is rounded to the nearest integer value (at the specified +/// digit) that is less than or equal to the absolute value of the input \\(x\\). An alias for +/// this rounding mode is [`truncate`](#truncate). +/// - `RoundDown`: The input \\(x\\) is rounded to the nearest integer value (at the specified +/// digit) that is less than or equal to the input \\(x\\). An alias for this rounding mode is +/// [`floor`](#floor). +/// - `RoundUp`: The input \\(x\\) is rounded to the nearest integer value (at the specified +/// digit) that is larger than or equal to the input \\(x\\). An alias for this rounding mode +/// is [`ceiling`](#ceiling). /// ///
/// Details /// -/// The `RoundNearest` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundNearest` rounding mode, rounds \\(12.0654\\) to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.1\\) for 1 digit after the decimal point (`digits = 1`) +/// - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// -/// The `RoundTiesAway` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundTiesAway` rounding mode, rounds \\(12.0654\\) to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.1\\) for 1 digit after the decimal point (`digits = 1`) +/// - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// -/// The `RoundTiesUp` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundTiesUp` rounding mode, rounds \\(12.0654\\) to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.1\\) for 1 digits after the decimal point (`digits = 1`) +/// - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// -/// The `RoundToZero` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundToZero` rounding mode, rounds \\(12.0654\\) to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.0\\) for 1 digit after the decimal point (`digits = 1`) +/// - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// -/// The `RoundDown` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundDown` rounding mode, rounds \\(12.0654\\) to: +/// - \\(12.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.0\\) for 1 digits after the decimal point (`digits = 1`) +/// - \\(12.06\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.065\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(10.0\\) for 1 digit before the decimal point (`digits = -1`) +/// - \\(0.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(0.0\\) for 3 digits before the decimal point (`digits = -3`) /// -/// The `RoundUp` rounding mode, rounds $$12.0654$$ to: -/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) +/// The `RoundUp` rounding mode, rounds \\(12.0654\\) to: +/// - \\(13.0\\) for 0 digits after the decimal point (`digits = 0`) +/// - \\(12.1\\) for 1 digit after the decimal point (`digits = 1`) +/// - \\(12.07\\) for 2 digits after the decimal point (`digits = 2`) +/// - \\(12.066\\) for 3 digits after the decimal point (`digits = 3`) /// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) -/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) +/// It is also possible to specify a negative number of digits. In that case, the negative +/// number refers to the digits before the decimal point. +/// - \\(20.0\\) for 1 digit places before the decimal point (`digit = -1`) +/// - \\(100.0\\) for 2 digits before the decimal point (`digits = -2`) +/// - \\(1000.0\\) for 3 digits before the decimal point (`digits = -3`) /// ///
/// @@ -475,7 +503,7 @@ fn do_ceiling(a: Float) -> Float /// \forall x \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}. /// \\] /// -/// The function takes an input $$x$$ and returns a positive float value. +/// The function takes an input \\(x\\) and returns a positive float value. /// ///
/// @@ -504,7 +532,7 @@ pub fn float_absolute_value(x: Float) -> Float { /// \forall x \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}. /// \\] /// -/// The function takes an input $$x$$ and returns a positive integer value. +/// The function takes an input \\(x\\) and returns a positive integer value. /// ///
/// @@ -533,7 +561,7 @@ pub fn int_absolute_value(x: Int) -> Int { /// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}. /// \\] /// -/// The function takes two inputs $$x$$ and $$y$$ and returns a positive float +/// The function takes two inputs \\(x\\) and \\(y\\) and returns a positive float /// value which is the the absolute difference of the inputs. /// ///
@@ -574,7 +602,7 @@ pub fn float_absolute_difference(a: Float, b: Float) -> Float { /// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. /// \\] /// -/// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer +/// The function takes two inputs \\(x\\) and \\(y\\) and returns a positive integer /// value which is the the absolute difference of the inputs. /// ///
@@ -609,7 +637,7 @@ pub fn int_absolute_difference(a: Int, b: Int) -> Int { /// /// /// -/// The function takes an input $$x \in \mathbb{R}$$ and returns the sign of +/// The function takes an input \\(x \in \mathbb{R}\\) and returns the sign of /// the input, indicating whether it is positive (+1.0), negative (-1.0), or /// zero (0.0). /// @@ -645,7 +673,7 @@ fn do_float_sign(a: Float) -> Float /// /// /// -/// The function takes an input $$x \in \mathbb{Z}$$ and returns the sign of +/// The function takes an input \\(x \in \mathbb{Z}\\) and returns the sign of /// the input, indicating whether it is positive (+1), negative (-1), or zero /// (0). /// @@ -681,8 +709,8 @@ fn do_int_sign(a: Int) -> Int /// /// /// -/// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$ -/// such that it has the same sign as $$y$$. +/// The function takes two arguments \\(x, y \in \mathbb{R}\\) and returns \\(x\\) +/// such that it has the same sign as \\(y\\). /// /// /// -/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$ -/// such that it has the same sign as $$y$$. +/// The function takes two arguments \\(x, y \in \mathbb{Z}\\) and returns \\(x\\) +/// such that it has the same sign as \\(y\\). /// /// /// -/// The function flips the sign of a given input value $$x \in \mathbb{R}$$. +/// The function flips the sign of a given input value \\(x \in \mathbb{R}\\). /// /// /// -/// The function flips the sign of a given input value $$x \in \mathbb{Z}$$. +/// The function flips the sign of a given input value \\(x \in \mathbb{Z}\\). /// /// /// -/// The minimum function takes two arguments $$x, y$$ along with a function -/// for comparing $$x, y$$. The function returns the smallest of the two given +/// The minimum function takes two arguments \\(x, y\\) along with a function +/// for comparing \\(x, y\\). The function returns the smallest of the two given /// values. /// ///
@@ -815,8 +843,8 @@ pub fn minimum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { /// /// /// -/// The maximum function takes two arguments $$x, y$$ along with a function -/// for comparing $$x, y$$. The function returns the largest of the two given +/// The maximum function takes two arguments \\(x, y\\) along with a function +/// for comparing \\(x, y\\). The function returns the largest of the two given /// values. /// ///
@@ -855,8 +883,8 @@ pub fn maximum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { /// /// /// -/// The minmax function takes two arguments $$x, y$$ along with a function -/// for comparing $$x, y$$. The function returns a tuple with the smallest +/// The minmax function takes two arguments \\(x, y\\) along with a function +/// for comparing \\(x, y\\). The function returns a tuple with the smallest /// value first and largest second. /// ///
diff --git a/src/gleam_community/maths/predicates.gleam b/src/gleam_community/maths/predicates.gleam index 940f3a9..9cdd74c 100644 --- a/src/gleam_community/maths/predicates.gleam +++ b/src/gleam_community/maths/predicates.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -54,8 +54,8 @@ import gleam_community/maths/piecewise /// /// /// -/// Determine if a given value $$a$$ is close to or equivalent to a reference value -/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance +/// Determine if a given value \\(a\\) is close to or equivalent to a reference value +/// \\(b\\) based on supplied relative \\(r_{tol}\\) and absolute \\(a_{tol}\\) tolerance /// values. The equivalance of the two given values are then determined based on /// the equation: /// @@ -221,8 +221,8 @@ fn do_ceiling(a: Float) -> Float /// /// /// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a -/// power of another integer value $$y \in \mathbb{Z}$$. +/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is a +/// power of another integer value \\(y \in \mathbb{Z}\\). /// ///
/// Example: @@ -261,7 +261,7 @@ pub fn is_power(x: Int, y: Int) -> Bool { /// /// /// -/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a +/// A function that tests whether a given integer value \\(n \in \mathbb{Z}\\) is a /// perfect number. A number is perfect if it is equal to the sum of its proper /// positive divisors. /// @@ -269,8 +269,8 @@ pub fn is_power(x: Int, y: Int) -> Bool { /// Details /// /// For example: -/// - $$6$$ is a perfect number since the divisors of 6 are $$1 + 2 + 3 = 6$$. -/// - $$28$$ is a perfect number since the divisors of 28 are $$1 + 2 + 4 + 7 + 14 = 28$$ +/// - \\(6\\) is a perfect number since the divisors of 6 are \\(1 + 2 + 3 = 6\\). +/// - \\(28\\) is a perfect number since the divisors of 28 are \\(1 + 2 + 4 + 7 + 14 = 28\\). /// ///
/// @@ -314,7 +314,7 @@ fn do_sum(arr: List(Int)) -> Int { /// /// /// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is even. +/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is even. /// ///
/// Example: @@ -347,7 +347,7 @@ pub fn is_even(x: Int) -> Bool { /// /// /// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is odd. +/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is odd. /// ///
/// Example: @@ -380,11 +380,11 @@ pub fn is_odd(x: Int) -> Bool { /// /// /// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a +/// A function that tests whether a given integer value \\(x \in \mathbb{Z}\\) is a /// prime number. A prime number is a natural number greater than 1 that has no /// positive divisors other than 1 and itself. /// -/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime. +/// The function uses the Miller-Rabin primality test to assess if \\(x\\) is prime. /// It is a probabilistic test, so it can mistakenly identify a composite number /// as prime. However, the probability of such errors decreases with more testing /// iterations (the function uses 64 iterations internally, which is typically @@ -395,9 +395,10 @@ pub fn is_odd(x: Int) -> Bool { /// Details /// /// Examples of prime numbers: -/// - $$2$$ is a prime number since it has only two divisors: $$1$$ and $$2$$. -/// - $$7$$ is a prime number since it has only two divisors: $$1$$ and $$7$$. -/// - $$4$$ is not a prime number since it has divisors other than $$1$$ and itself, such as $$2$$. +/// - \\(2\\) is a prime number since it has only two divisors: \\(1\\) and \\(2\\). +/// - \\(7\\) is a prime number since it has only two divisors: \\(1\\) and \\(7\\). +/// - \\(4\\) is not a prime number since it has divisors other than \\(1\\) and itself, such +/// as \\(2\\). /// ///
/// @@ -474,8 +475,8 @@ fn powmod_with_check(base: Int, exponent: Int, modulus: Int) -> Int { /// /// /// -/// A function that tests whether a given real number $$x \in \mathbb{R}$$ is strictly -/// between two other real numbers, $$a,b \in \mathbb{R}$$, such that $$a < x < b$$. +/// A function that tests whether a given real number \\(x \in \mathbb{R}\\) is strictly +/// between two other real numbers, \\(a,b \in \mathbb{R}\\), such that \\(a < x < b\\). /// ///
/// Example: @@ -511,15 +512,15 @@ pub fn is_between(x: Float, lower: Float, upper: Float) -> Bool { /// /// /// -/// A function that tests whether a given integer $$n \in \mathbb{Z}$$ is divisible by another -/// integer $$d \in \mathbb{Z}$$, such that $$n \mod d = 0$$. +/// A function that tests whether a given integer \\(n \in \mathbb{Z}\\) is divisible by another +/// integer \\(d \in \mathbb{Z}\\), such that \\(n \mod d = 0\\). /// ///
/// Details /// /// For example: -/// - $$n = 10$$ is divisible by $$d = 2$$ because $$10 \mod 2 = 0$$. -/// - $$n = 7$$ is not divisible by $$d = 3$$ because $$7 \mod 3 \neq 0$$. +/// - \\(n = 10\\) is divisible by \\(d = 2\\) because \\(10 \mod 2 = 0\\). +/// - \\(n = 7\\) is not divisible by \\(d = 3\\) because \\(7 \mod 3 \neq 0\\). /// ///
/// @@ -554,15 +555,16 @@ pub fn is_divisible(n: Int, d: Int) -> Bool { /// /// /// -/// A function that tests whether a given integer $$m \in \mathbb{Z}$$ is a multiple of another -/// integer $$k \in \mathbb{Z}$$, such that $$m = k \times q \quad q \in \mathbb{Z}$$. +/// A function that tests whether a given integer \\(m \in \mathbb{Z}\\) is a multiple of another +/// integer \\(k \in \mathbb{Z}\\), such that \\(m = k \times q\\), with \\(q \in \mathbb{Z}\\). /// ///
/// Details /// /// For example: -/// - $$m = 15$$ is a multiple of $$k = 5$$ because $$15 = 5 \times 3$$. -/// - $$m = 14$$ is not a multiple of $$k = 5$$ because $$14 \div 5$$ does not yield an integer quotient. +/// - \\(m = 15\\) is a multiple of \\(k = 5\\) because \\(15 = 5 \times 3\\). +/// - \\(m = 14\\) is not a multiple of \\(k = 5\\) because \\(14 \div 5\\) does not yield an +/// integer quotient. /// ///
/// diff --git a/src/gleam_community/maths/sequences.gleam b/src/gleam_community/maths/sequences.gleam index 3512152..9ffe6ec 100644 --- a/src/gleam_community/maths/sequences.gleam +++ b/src/gleam_community/maths/sequences.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -33,7 +33,7 @@ //// * [`geometric_space`](#geometric_space) //// -import gleam/list +import gleam/iterator import gleam_community/maths/conversion import gleam_community/maths/elementary import gleam_community/maths/piecewise @@ -44,29 +44,32 @@ import gleam_community/maths/piecewise /// /// /// -/// The function returns a list with evenly spaced values within a given interval -/// based on a start, stop value and a given increment (step-length) between -/// consecutive values. The list returned includes the given start value but -/// excludes the stop value. -/// +/// The function returns an iterator generating evenly spaced values within a given interval. +/// based on a start value but excludes the stop value. The spacing between values is determined +/// by the step size provided. The function supports both positive and negative step values. +/// ///
/// Example: /// +/// import gleam/iterator /// import gleeunit/should /// import gleam_community/maths/sequences /// /// pub fn example () { /// sequences.arange(1.0, 5.0, 1.0) +/// |> iterator.to_list() /// |> should.equal([1.0, 2.0, 3.0, 4.0]) /// /// // No points returned since -/// // start smaller than stop and positive step +/// // start is smaller than stop and the step is positive /// sequences.arange(5.0, 1.0, 1.0) +/// |> iterator.to_list() /// |> should.equal([]) /// /// // Points returned since /// // start smaller than stop but negative step /// sequences.arange(5.0, 1.0, -1.0) +/// |> iterator.to_list() /// |> should.equal([5.0, 4.0, 3.0, 2.0]) /// } ///
@@ -77,19 +80,29 @@ import gleam_community/maths/piecewise /// /// /// -pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) { +pub fn arange( + start: Float, + stop: Float, + step: Float, +) -> iterator.Iterator(Float) { case start >=. stop && step >. 0.0 || start <=. stop && step <. 0.0 { - True -> [] + True -> iterator.empty() False -> { - let direction: Float = case start <=. stop { - True -> 1.0 - False -> -1.0 + let direction = case start <=. stop { + True -> { + 1.0 + } + False -> { + -1.0 + } } - let step_abs: Float = piecewise.float_absolute_value(step) - let num: Float = piecewise.float_absolute_value(start -. stop) /. step_abs + let step_abs = piecewise.float_absolute_value(step) + let num = + piecewise.float_absolute_value(start -. stop) /. step_abs + |> conversion.float_to_int() - list.range(0, conversion.float_to_int(num) - 1) - |> list.map(fn(i: Int) -> Float { + iterator.range(0, num - 1) + |> iterator.map(fn(i: Int) { start +. conversion.int_to_float(i) *. step_abs *. direction }) } @@ -102,22 +115,30 @@ pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) { /// /// /// -/// Generate a linearly spaced list of points over a specified interval. The -/// endpoint of the interval can optionally be included/excluded. -/// +/// The function returns an iterator for generating linearly spaced points over a specified +/// interval. The endpoint of the interval can optionally be included/excluded. The number of +/// points and whether the endpoint is included determine the spacing between values. +/// ///
/// Example: /// +/// import gleam/iterator /// import gleeunit/should /// import gleam_community/maths/elementary /// import gleam_community/maths/sequences /// import gleam_community/maths/predicates /// /// pub fn example () { -/// let assert Ok(tol) = elementary.power(-10.0, -6.0) -/// let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, True) +/// let assert Ok(tol) = elementary.power(10.0, -6.0) +/// let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, True) /// let assert Ok(result) = -/// predicates.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) +/// predicates.all_close( +/// linspace |> iterator.to_list(), +/// [10.0, 12.5, 15.0, 17.5, 20.0], +/// 0.0, +/// tol, +/// ) +/// /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() @@ -139,38 +160,32 @@ pub fn linear_space( stop: Float, num: Int, endpoint: Bool, -) -> Result(List(Float), String) { +) -> Result(iterator.Iterator(Float), String) { let direction: Float = case start <=. stop { True -> 1.0 False -> -1.0 } - case num > 0 { - True -> - case endpoint { - True -> { - let increment: Float = - piecewise.float_absolute_value(start -. stop) - /. conversion.int_to_float(num - 1) - list.range(0, num - 1) - |> list.map(fn(i: Int) -> Float { - start +. conversion.int_to_float(i) *. increment *. direction - }) - |> Ok - } - False -> { - let increment: Float = - piecewise.float_absolute_value(start -. stop) - /. conversion.int_to_float(num) - list.range(0, num - 1) - |> list.map(fn(i: Int) -> Float { - start +. conversion.int_to_float(i) *. increment *. direction - }) - |> Ok - } - } + let increment = case endpoint { + True -> { + piecewise.float_absolute_value(start -. stop) + /. conversion.int_to_float(num - 1) + } + False -> { + piecewise.float_absolute_value(start -. stop) + /. conversion.int_to_float(num) + } + } + case num > 0 { + True -> { + iterator.range(0, num - 1) + |> iterator.map(fn(i: Int) -> Float { + start +. conversion.int_to_float(i) *. increment *. direction + }) + |> Ok + } False -> - "Invalid input: num < 0. Valid input is num > 0." + "Invalid input: num < 1. Valid input is num >= 1." |> Error } } @@ -181,22 +196,29 @@ pub fn linear_space( /// /// /// -/// Generate a logarithmically spaced list of points over a specified interval. The -/// endpoint of the interval can optionally be included/excluded. -/// +/// The function returns an iterator of logarithmically spaced points over a specified interval. +/// The endpoint of the interval can optionally be included/excluded. The number of points, base, +/// and whether the endpoint is included determine the spacing between values. +/// ///
/// Example: /// +/// import gleam/iterator /// import gleeunit/should /// import gleam_community/maths/elementary /// import gleam_community/maths/sequences /// import gleam_community/maths/predicates /// /// pub fn example () { -/// let assert Ok(tol) = elementary.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(10.0, -6.0) /// let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0) /// let assert Ok(result) = -/// predicates.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// predicates.all_close( +/// logspace |> iterator.to_list(), +/// [10.0, 100.0, 1000.0], +/// 0.0, +/// tol, +/// ) /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() @@ -219,19 +241,19 @@ pub fn logarithmic_space( num: Int, endpoint: Bool, base: Float, -) -> Result(List(Float), String) { +) -> Result(iterator.Iterator(Float), String) { case num > 0 { True -> { let assert Ok(linspace) = linear_space(start, stop, num, endpoint) linspace - |> list.map(fn(i: Float) -> Float { + |> iterator.map(fn(i: Float) -> Float { let assert Ok(result) = elementary.power(base, i) result }) |> Ok } False -> - "Invalid input: num < 0. Valid input is num > 0." + "Invalid input: num < 1. Valid input is num >= 1." |> Error } } @@ -242,25 +264,30 @@ pub fn logarithmic_space( /// /// /// -/// The function returns a list of numbers spaced evenly on a log scale (a -/// geometric progression). Each point in the list is a constant multiple of the -/// previous. The function is similar to the -/// [`logarithmic_space`](#logarithmic_space) function, but with endpoints +/// The function returns an iterator of numbers spaced evenly on a log scale (a geometric +/// progression). Each point in the list is a constant multiple of the previous. The function is +/// similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints /// specified directly. /// ///
/// Example: /// +/// import gleam/iterator /// import gleeunit/should /// import gleam_community/maths/elementary /// import gleam_community/maths/sequences /// import gleam_community/maths/predicates /// /// pub fn example () { -/// let assert Ok(tol) = elementary.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(10.0, -6.0) /// let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True) /// let assert Ok(result) = -/// predicates.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// predicates.all_close( +/// logspace |> iterator.to_list(), +/// [10.0, 100.0, 1000.0], +/// 0.0, +/// tol, +/// ) /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() @@ -289,7 +316,7 @@ pub fn geometric_space( stop: Float, num: Int, endpoint: Bool, -) -> Result(List(Float), String) { +) -> Result(iterator.Iterator(Float), String) { case start == 0.0 || stop == 0.0 { True -> "Invalid input: Neither 'start' nor 'stop' can be zero, as they must be non-zero for logarithmic calculations." @@ -302,7 +329,7 @@ pub fn geometric_space( logarithmic_space(log_start, log_stop, num, endpoint, 10.0) } False -> - "Invalid input: num < 0. Valid input is num > 0." + "Invalid input: num < 1. Valid input is num >= 1." |> Error } } diff --git a/src/gleam_community/maths/special.gleam b/src/gleam_community/maths/special.gleam index 2b79bae..f6e3438 100644 --- a/src/gleam_community/maths/special.gleam +++ b/src/gleam_community/maths/special.gleam @@ -1,6 +1,6 @@ -//// -//// -//// +//// +//// +//// //// @@ -101,7 +101,7 @@ pub fn erf(x: Float) -> Float { /// /// /// The gamma function over the real numbers. The function is essentially equal to -/// the factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$ +/// the factorial for any positive integer argument: \\(\Gamma(n) = (n - 1)!\\) /// /// The implemented gamma function is approximated through Lanczos approximation /// using the same coefficients used by the GNU Scientific Library. diff --git a/test/gleam_community/maths/combinatorics_test.gleam b/test/gleam_community/maths/combinatorics_test.gleam index cef437b..087122f 100644 --- a/test/gleam_community/maths/combinatorics_test.gleam +++ b/test/gleam_community/maths/combinatorics_test.gleam @@ -6,11 +6,11 @@ import gleam_community/maths/combinatorics import gleeunit/should pub fn int_factorial_test() { - // Invalid input gives an error + // Invalid input gives an error (factorial of negative number) combinatorics.factorial(-1) |> should.be_error() - // Valid input returns a result + // Valid inputs for factorial of small numbers combinatorics.factorial(0) |> should.equal(Ok(1)) @@ -28,70 +28,120 @@ pub fn int_factorial_test() { } pub fn int_combination_test() { - // Invalid input gives an error - // Error on: n = -1 < 0 - combinatorics.combination( - -1, - 1, - option.Some(combinatorics.WithoutRepetitions), - ) + // Invalid input: k < 0 should return an error + combinatorics.combination(1, -1, option.None) |> should.be_error() - // Valid input returns a result + // Invalid input: n < 0 should return an error + combinatorics.combination(-1, 1, option.None) + |> should.be_error() + + // Valid input: k > n without repetition gives 0 combinations + combinatorics.combination(2, 3, option.Some(combinatorics.WithoutRepetitions)) + |> should.equal(Ok(0)) + + // Valid input: k > n with repetition allowed + combinatorics.combination(2, 3, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(4)) + + // Valid input: zero combinations (k=0) should always yield 1 combination combinatorics.combination(4, 0, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(1)) + combinatorics.combination(4, 0, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(1)) + + // Valid input: k = n without repetition gives 1 combination combinatorics.combination(4, 4, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(1)) + // Valid input: k = n with repetition allows more combinations + combinatorics.combination(4, 4, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(35)) + + // Valid input: k < n without and with repetition combinatorics.combination(4, 2, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(6)) + combinatorics.combination(4, 2, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(10)) + + // Valid input with larger values of n and k combinatorics.combination(7, 5, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(21)) - // NOTE: Tests with the 'combination' function that produce values that - // exceed precision of the JavaScript 'Number' primitive will result in - // errors + + combinatorics.combination(7, 5, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(462)) + // NOTE: Tests with the 'combination' function that produce values that exceed + // precision of the JavaScript 'Number' primitive will result in errors } pub fn math_permutation_test() { - // Invalid input gives an error - // Error on: n = -1 < 0 - combinatorics.permutation( - -1, - 1, - option.Some(combinatorics.WithoutRepetitions), - ) + // Invalid input: k < 0 should return an error + combinatorics.permutation(1, -1, option.None) |> should.be_error() - // Valid input returns a result + // Invalid input: n < 0 should return an error + combinatorics.permutation(-1, 1, option.None) + |> should.be_error() + + // Valid input: k > n without repetition gives 0 permutations + combinatorics.permutation(2, 3, option.Some(combinatorics.WithoutRepetitions)) + |> should.equal(Ok(0)) + + // Valid input: k > n with repetition allowed gives non-zero permutations + combinatorics.permutation(2, 3, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(8)) + + // Valid input: k = 0 should always yield 1 permutation combinatorics.permutation(4, 0, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(1)) + combinatorics.permutation(4, 0, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(1)) + + // Valid input: k = n permutations without repetition combinatorics.permutation(4, 4, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(24)) + // Valid input: k = n permutations with repetition + combinatorics.permutation(4, 4, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(256)) + + // Valid input: k < n permutations without and with repetition combinatorics.permutation(4, 2, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(12)) + combinatorics.permutation(4, 2, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(16)) + + // Valid input with larger values of n and k combinatorics.permutation(6, 2, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(30)) + combinatorics.permutation(6, 2, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(36)) + combinatorics.permutation(6, 3, option.Some(combinatorics.WithoutRepetitions)) |> should.equal(Ok(120)) + + combinatorics.permutation(6, 3, option.Some(combinatorics.WithRepetitions)) + |> should.equal(Ok(216)) } pub fn list_cartesian_product_test() { - // An empty lists returns an empty list - [] - |> combinatorics.cartesian_product([]) - |> should.equal([]) + // An empty list returns an empty list as the Cartesian product + let xset = set.from_list([]) + let yset = set.from_list([]) + let expected_result = set.from_list([]) + xset + |> combinatorics.cartesian_product(yset) + |> should.equal(expected_result) - // Test with some arbitrary inputs - [1, 2, 3] - |> combinatorics.cartesian_product([1, 2, 3]) - |> set.from_list() - |> should.equal( + // Cartesian product of two sets with the same elements + let xset = set.from_list([1, 2, 3]) + let yset = set.from_list([1, 2, 3]) + let expected_result = set.from_list([ #(1, 1), #(1, 2), @@ -102,142 +152,549 @@ pub fn list_cartesian_product_test() { #(3, 1), #(3, 2), #(3, 3), + ]) + xset + |> combinatorics.cartesian_product(yset) + |> should.equal(expected_result) + + // Cartesian product with floating-point numbers + let xset = set.from_list([1.0, 10.0]) + let yset = set.from_list([1.0, 2.0]) + let expected_result = + set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) + xset + |> combinatorics.cartesian_product(yset) + |> should.equal(expected_result) + + // Cartesian product of sets with different sizes + let xset = set.from_list([1.0, 10.0, 100.0]) + let yset = set.from_list([1.0, 2.0]) + let expected_result = + set.from_list([ + #(1.0, 1.0), + #(1.0, 2.0), + #(10.0, 1.0), + #(10.0, 2.0), + #(100.0, 1.0), + #(100.0, 2.0), + ]) + xset + |> combinatorics.cartesian_product(yset) + |> should.equal(expected_result) + + // Cartesian product with different types (strings) + let xset = set.from_list(["a", "y", "z"]) + let yset = set.from_list(["a", "x"]) + let expected_result = + set.from_list([ + #("a", "a"), + #("a", "x"), + #("y", "a"), + #("y", "x"), + #("z", "a"), + #("z", "x"), + ]) + xset + |> combinatorics.cartesian_product(yset) + |> should.equal(expected_result) +} + +pub fn list_permutation_test() { + // Invalid input: k < 0 should return an error for an empty list + [] + |> combinatorics.list_permutation(-1, option.None) + |> should.be_error() + + // Invalid input: k > n should return an error without repetition + [1, 2] + |> combinatorics.list_permutation( + 3, + option.Some(combinatorics.WithoutRepetitions), + ) + |> should.be_error() + + // Valid input: An empty list returns a single empty permutation + let assert Ok(permutations) = + [] + |> combinatorics.list_permutation(0, option.None) + permutations + |> iterator.to_list() + |> should.equal([[]]) + + // Singleton list returns a single permutation regardless of repetition settings + let assert Ok(permutations) = + ["a"] + |> combinatorics.list_permutation( + 1, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> should.equal([["a"]]) + + let assert Ok(permutations) = + ["a"] + |> combinatorics.list_permutation( + 1, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> should.equal([["a"]]) + + // 4-permutations of a single element repeats it 4 times + let assert Ok(permutations) = + ["a"] + |> combinatorics.list_permutation( + 4, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> should.equal([["a", "a", "a", "a"]]) + + // 2-permutations of [1, 2] without repetition + let assert Ok(permutations) = + [1, 2] + |> combinatorics.list_permutation( + 2, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> set.from_list() + |> should.equal(set.from_list([[1, 2], [2, 1]])) + + // 2-permutations of [1, 2] with repetition + let assert Ok(permutations) = + [1, 2] + |> combinatorics.list_permutation( + 2, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> set.from_list() + |> should.equal(set.from_list([[1, 1], [1, 2], [2, 2], [2, 1]])) + + // 3-permutations of [1, 2, 3] without repetition + let assert Ok(permutations) = + [1, 2, 3] + |> combinatorics.list_permutation( + 3, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> set.from_list() + |> should.equal( + set.from_list([ + [1, 2, 3], + [2, 1, 3], + [3, 1, 2], + [1, 3, 2], + [2, 3, 1], + [3, 2, 1], ]), ) - [1.0, 10.0] - |> combinatorics.cartesian_product([1.0, 2.0]) + // 3-permutations of [1, 2, 3] with repetition + let assert Ok(permutations) = + [1, 2, 3] + |> combinatorics.list_permutation( + 3, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() |> set.from_list() + |> should.equal( + set.from_list([ + [1, 1, 1], + [1, 1, 2], + [1, 1, 3], + [1, 2, 1], + [1, 2, 2], + [1, 2, 3], + [1, 3, 1], + [1, 3, 2], + [1, 3, 3], + [2, 1, 1], + [2, 1, 2], + [2, 1, 3], + [2, 2, 1], + [2, 2, 2], + [2, 2, 3], + [2, 3, 1], + [2, 3, 2], + [2, 3, 3], + [3, 1, 1], + [3, 1, 2], + [3, 1, 3], + [3, 2, 1], + [3, 2, 2], + [3, 2, 3], + [3, 3, 1], + [3, 3, 2], + [3, 3, 3], + ]), + ) + + // Repeated elements are treated as distinct in permutations without repetition + let assert Ok(permutations) = + [1.0, 1.0] + |> combinatorics.list_permutation( + 2, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> should.equal([[1.0, 1.0], [1.0, 1.0]]) + + // Repeated elements allow more possibilities when repetition is allowed + let assert Ok(permutations) = + [1.0, 1.0] + |> combinatorics.list_permutation( + 2, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> should.equal([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]) + + // Large inputs: Ensure the correct number of permutations is generated + let assert Ok(permutations) = + ["a", "b", "c", "d", "e"] + |> combinatorics.list_permutation( + 5, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> list.length() + |> should.equal(120) + + let assert Ok(permutations) = + ["a", "b", "c", "d", "e"] + |> combinatorics.list_permutation( + 5, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> list.length() + |> should.equal(3125) +} + +pub fn permutation_alignment_test() { + // Test: Number of generated permutations should match the expected count + // Without repetitions + let arr = ["a", "b", "c", "d", "e", "f"] + let length = list.length(arr) + + let assert Ok(number_of_permutations) = + combinatorics.permutation( + length, + length, + option.Some(combinatorics.WithoutRepetitions), + ) + let assert Ok(permutations) = + arr + |> combinatorics.list_permutation( + length, + option.Some(combinatorics.WithoutRepetitions), + ) + permutations + |> iterator.to_list() + |> list.length() + |> should.equal(number_of_permutations) + + // With repetitions + let arr = ["a", "b", "c", "d", "e", "f"] + let length = list.length(arr) + + let assert Ok(number_of_permutations) = + combinatorics.permutation( + length, + length, + option.Some(combinatorics.WithRepetitions), + ) + let assert Ok(permutations) = + arr + |> combinatorics.list_permutation( + length, + option.Some(combinatorics.WithRepetitions), + ) + permutations + |> iterator.to_list() + |> list.length() + |> should.equal(number_of_permutations) +} + +pub fn list_combination_test() { + // Invalid input: k < 0 should return an error for an empty list + [] + |> combinatorics.list_combination(-1, option.None) + |> should.be_error() + + // Invalid input: k > n should return an error without repetition + [1, 2] + |> combinatorics.list_combination( + 3, + option.Some(combinatorics.WithoutRepetitions), + ) + |> should.be_error() + + // Valid input: k > n with repetition allowed + let assert Ok(combinations) = + [1, 2] + |> combinatorics.list_combination( + 3, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]]) + + // Valid input: Empty list should return a single empty combination + let assert Ok(combinations) = + [] + |> combinatorics.list_combination( + 0, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[]]) + + let assert Ok(combinations) = + [] + |> combinatorics.list_combination( + 0, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[]]) + + // 1-combination of [1, 2] without and with repetition + let assert Ok(combinations) = + [1, 2] + |> combinatorics.list_combination( + 1, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1], [2]]) + + let assert Ok(combinations) = + [1, 2] + |> combinatorics.list_combination( + 1, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1], [2]]) + + // 2-combination of [1, 2] without and with repetition + let assert Ok(combinations) = + [1, 2] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1, 2]]) + + let assert Ok(combinations) = + [1, 2] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1, 1], [1, 2], [2, 2]]) + + // 2-combination of [1, 2, 3, 4] without and with repetition + let assert Ok(combinations) = + [1, 2, 3, 4] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> set.from_list() + |> should.equal( + set.from_list([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]), + ) + + let assert Ok(combinations) = + [1, 2, 3, 4] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> set.from_list() + |> should.equal( + set.from_list([ + [1, 1], + [1, 2], + [1, 3], + [1, 4], + [2, 2], + [2, 3], + [2, 4], + [3, 3], + [3, 4], + [4, 4], + ]), + ) + + // 3-combination of [1, 2, 3, 4] without repetition + let assert Ok(combinations) = + [1, 2, 3, 4] + |> combinatorics.list_combination( + 3, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> set.from_list() + |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]])) + + // 3-combination of [1, 2, 3] with repetition + let assert Ok(combinations) = + [1, 2, 3] + |> combinatorics.list_combination( + 3, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> set.from_list() + |> should.equal( + set.from_list([ + [1, 1, 1], + [1, 1, 2], + [1, 1, 3], + [1, 2, 2], + [1, 2, 3], + [1, 3, 3], + [2, 2, 2], + [2, 2, 3], + [2, 3, 3], + [3, 3, 3], + ]), + ) + + // Combinations treat repeated elements as distinct in certain scenarios + let assert Ok(combinations) = + [1.0, 1.0] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1.0, 1.0]]) + + // Repetition creates more possibilities even with identical elements + let assert Ok(combinations) = + [1.0, 1.0] + |> combinatorics.list_combination( + 2, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> should.equal([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]) + + // Large input: Ensure correct number of combinations is generated + let assert Ok(combinations) = + ["a", "b", "c", "d", "e"] + |> combinatorics.list_combination( + 5, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> list.length() + |> should.equal(1) + + let assert Ok(combinations) = + ["a", "b", "c", "d", "e"] + |> combinatorics.list_combination( + 5, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> list.length() + |> should.equal(126) +} + +pub fn combination_alignment_test() { + // Test: Number of generated combinations should match the expected count + // Without repetitions + let arr = ["a", "b", "c", "d", "e", "f"] + let length = list.length(arr) + + let assert Ok(number_of_combinations) = + combinatorics.combination( + length, + length, + option.Some(combinatorics.WithoutRepetitions), + ) + let assert Ok(combinations) = + arr + |> combinatorics.list_combination( + length, + option.Some(combinatorics.WithoutRepetitions), + ) + combinations + |> iterator.to_list() + |> list.length() + |> should.equal(number_of_combinations) + + // With repetitions + let arr = ["a", "b", "c", "d", "e", "f"] + let length = list.length(arr) + + let assert Ok(number_of_combinations) = + combinatorics.combination( + length, + length, + option.Some(combinatorics.WithRepetitions), + ) + let assert Ok(combinations) = + arr + |> combinatorics.list_combination( + length, + option.Some(combinatorics.WithRepetitions), + ) + combinations + |> iterator.to_list() + |> list.length() + |> should.equal(number_of_combinations) +} + +pub fn example_test() { + // Cartesian product of two empty sets + set.from_list([]) + |> combinatorics.cartesian_product(set.from_list([])) + |> should.equal(set.from_list([])) + + // Cartesian product of two sets with numeric values + set.from_list([1.0, 10.0]) + |> combinatorics.cartesian_product(set.from_list([1.0, 2.0])) |> should.equal( set.from_list([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]), ) } -// pub fn list_permutation_test() { -// // An empty lists returns one (empty) permutation -// [] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> should.equal([[]]) - -// // Singleton returns one (singleton) permutation -// // Also works regardless of type of list elements -// ["a"] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> should.equal([["a"]]) - -// // Test with some arbitrary inputs -// [1, 2] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> set.from_list() -// |> should.equal(set.from_list([[1, 2], [2, 1]])) - -// // Test with some arbitrary inputs -// [1, 2, 3] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> set.from_list() -// |> should.equal( -// set.from_list([ -// [1, 2, 3], -// [2, 1, 3], -// [3, 1, 2], -// [1, 3, 2], -// [2, 3, 1], -// [3, 2, 1], -// ]), -// ) - -// // Repeated elements are treated as distinct for the -// // purpose of permutations, so two identical elements -// // will appear "both ways round" -// [1.0, 1.0] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> should.equal([[1.0, 1.0], [1.0, 1.0]]) - -// // This means lists with repeated elements return the -// // same number of permutations as ones without -// ["l", "e", "t", "t", "e", "r", "s"] -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> list.length() -// |> should.equal(5040) - -// // Test that the number of generate permutations of the given input list aligns with the computed -// // number of possible permutations (using the formula for the binomial coefficient) -// let arr = ["a", "b", "c", "x", "y", "z"] -// let length = list.length(arr) -// let assert Ok(permuations) = -// combinatorics.permutation( -// length, -// length, -// option.Some(combinatorics.WithoutRepetitions), -// ) - -// arr -// |> combinatorics.list_permutation() -// |> iterator.to_list() -// |> list.length() -// |> should.equal(permuations) -// } - -// pub fn list_combination_test() { -// // Invaldi input: A negative number returns an error -// [] -// |> combinatorics.list_combination(-1) -// |> should.be_error() - -// // Invalid input: k is larger than given input list, so it returns an error -// [1, 2] -// |> combinatorics.list_combination(3) -// |> should.be_error() - -// // Valid input: An empty lists returns an empty list -// let assert Ok(combinations) = -// [] -// |> combinatorics.list_combination(0) - -// combinations -// |> iterator.to_list() -// |> should.equal([[]]) - -// // Test with some arbitrary but valid inputs -// let assert Ok(combinations) = -// [1, 2] -// |> combinatorics.list_combination(1) - -// combinations -// |> iterator.to_list() -// |> should.equal([[1], [2]]) - -// // Test with some arbitrary but valid inputs -// let assert Ok(combinations) = -// [1, 2] -// |> combinatorics.list_combination(2) - -// combinations -// |> iterator.to_list() -// |> should.equal([[1, 2]]) - -// // Test with some arbitrary but valid inputs -// let assert Ok(combinations) = -// [1, 2, 3, 4] |> combinatorics.list_combination(2) - -// combinations -// |> iterator.to_list() -// |> set.from_list() -// |> should.equal( -// set.from_list([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]), -// ) - -// // Test with some arbitrary but valid inputs -// let assert Ok(combinations) = -// [1, 2, 3, 4] |> combinatorics.list_combination(3) - -// combinations -// |> iterator.to_list() -// |> set.from_list() -// |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]])) -// } diff --git a/test/gleam_community/maths/sequences_test.gleam b/test/gleam_community/maths/sequences_test.gleam index 72ee74a..1571d4b 100644 --- a/test/gleam_community/maths/sequences_test.gleam +++ b/test/gleam_community/maths/sequences_test.gleam @@ -1,3 +1,4 @@ +import gleam/iterator import gleam/list import gleam_community/maths/elementary import gleam_community/maths/predicates @@ -12,14 +13,24 @@ pub fn float_list_linear_space_test() { // ---> With endpoint included let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, True) let assert Ok(result) = - predicates.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, 20.0, 30.0, 40.0, 50.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, True) let assert Ok(result) = - predicates.all_close(linspace, [10.0, 12.5, 15.0, 17.5, 20.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, 12.5, 15.0, 17.5, 20.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -29,7 +40,12 @@ pub fn float_list_linear_space_test() { // ----> Without endpoint included let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, False) let assert Ok(result) = - predicates.all_close(linspace, [10.0, 18.0, 26.0, 34.0, 42.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, 18.0, 26.0, 34.0, 42.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -37,7 +53,12 @@ pub fn float_list_linear_space_test() { let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, False) let assert Ok(result) = - predicates.all_close(linspace, [10.0, 12.0, 14.0, 16.0, 18.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, 12.0, 14.0, 16.0, 18.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -46,7 +67,12 @@ pub fn float_list_linear_space_test() { // Try with negative stop let assert Ok(linspace) = sequences.linear_space(10.0, -50.0, 5, False) let assert Ok(result) = - predicates.all_close(linspace, [10.0, -2.0, -14.0, -26.0, -38.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, -2.0, -14.0, -26.0, -38.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -54,7 +80,12 @@ pub fn float_list_linear_space_test() { let assert Ok(linspace) = sequences.linear_space(10.0, -20.0, 5, True) let assert Ok(result) = - predicates.all_close(linspace, [10.0, 2.5, -5.0, -12.5, -20.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [10.0, 2.5, -5.0, -12.5, -20.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -63,7 +94,12 @@ pub fn float_list_linear_space_test() { // Try with negative start let assert Ok(linspace) = sequences.linear_space(-10.0, 50.0, 5, False) let assert Ok(result) = - predicates.all_close(linspace, [-10.0, 2.0, 14.0, 26.0, 38.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [-10.0, 2.0, 14.0, 26.0, 38.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -71,7 +107,12 @@ pub fn float_list_linear_space_test() { let assert Ok(linspace) = sequences.linear_space(-10.0, 20.0, 5, True) let assert Ok(result) = - predicates.all_close(linspace, [-10.0, -2.5, 5.0, 12.5, 20.0], 0.0, tol) + predicates.all_close( + linspace |> iterator.to_list(), + [-10.0, -2.5, 5.0, 12.5, 20.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) @@ -90,7 +131,12 @@ pub fn float_list_logarithmic_space_test() { // - Positive start, stop, base let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 100.0, 1000.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -99,7 +145,12 @@ pub fn float_list_logarithmic_space_test() { let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, -10.0) let assert Ok(result) = - predicates.all_close(logspace, [-10.0, 100.0, -1000.0], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [-10.0, 100.0, -1000.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -108,7 +159,12 @@ pub fn float_list_logarithmic_space_test() { let assert Ok(logspace) = sequences.logarithmic_space(1.0, -3.0, 3, True, -10.0) let assert Ok(result) = - predicates.all_close(logspace, [-10.0, -0.1, -0.001], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [-10.0, -0.1, -0.001], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -117,7 +173,12 @@ pub fn float_list_logarithmic_space_test() { let assert Ok(logspace) = sequences.logarithmic_space(1.0, -3.0, 3, True, 10.0) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 0.1, 0.001], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -126,7 +187,12 @@ pub fn float_list_logarithmic_space_test() { let assert Ok(logspace) = sequences.logarithmic_space(-1.0, 3.0, 3, True, 10.0) let assert Ok(result) = - predicates.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [0.1, 10.0, 1000.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -136,7 +202,12 @@ pub fn float_list_logarithmic_space_test() { let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, False, 10.0) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 46.41588834, 215.443469], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -154,7 +225,12 @@ pub fn float_list_geometric_space_test() { // - Positive start, stop let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 100.0, 1000.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -162,7 +238,12 @@ pub fn float_list_geometric_space_test() { // - Positive start, negative stop let assert Ok(logspace) = sequences.geometric_space(10.0, 0.001, 3, True) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 0.1, 0.001], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -170,7 +251,12 @@ pub fn float_list_geometric_space_test() { // - Positive stop, negative start let assert Ok(logspace) = sequences.geometric_space(0.1, 1000.0, 3, True) let assert Ok(result) = - predicates.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [0.1, 10.0, 1000.0], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -179,7 +265,12 @@ pub fn float_list_geometric_space_test() { // - Positive start, stop let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, False) let assert Ok(result) = - predicates.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) + predicates.all_close( + logspace |> iterator.to_list(), + [10.0, 46.41588834, 215.443469], + 0.0, + tol, + ) result |> list.all(fn(x) { x == True }) |> should.be_true() @@ -199,31 +290,39 @@ pub fn float_list_geometric_space_test() { pub fn float_list_arange_test() { // Positive start, stop, step sequences.arange(1.0, 5.0, 1.0) + |> iterator.to_list() |> should.equal([1.0, 2.0, 3.0, 4.0]) sequences.arange(1.0, 5.0, 0.5) + |> iterator.to_list() |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) sequences.arange(1.0, 2.0, 0.25) + |> iterator.to_list() |> should.equal([1.0, 1.25, 1.5, 1.75]) // Reverse (switch start/stop largest/smallest value) sequences.arange(5.0, 1.0, 1.0) + |> iterator.to_list() |> should.equal([]) // Reverse negative step sequences.arange(5.0, 1.0, -1.0) + |> iterator.to_list() |> should.equal([5.0, 4.0, 3.0, 2.0]) // Positive start, negative stop, step sequences.arange(5.0, -1.0, -1.0) + |> iterator.to_list() |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) // Negative start, stop, step sequences.arange(-5.0, -1.0, -1.0) + |> iterator.to_list() |> should.equal([]) // Negative start, stop, positive step sequences.arange(-5.0, -1.0, 1.0) + |> iterator.to_list() |> should.equal([-5.0, -4.0, -3.0, -2.0]) }