diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index dd1e6b2..ee771a1 100644 --- a/src/gleam_community/maths/combinatorics.gleam +++ b/src/gleam_community/maths/combinatorics.gleam @@ -319,6 +319,18 @@ fn do_list_combination(arr: List(a), k: Int, prefix: List(a)) -> List(List(a)) { /// /// Generate all permutations of a given list. /// +/// 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. +/// +/// N.B. The output of this function is a list of size +/// factorial in the size of the input list. Caution is +/// advised on input lists longer than ~11 elements, which +/// may cause the VM to use unholy amounts of memory for +/// the output. +/// ///
/// Example: /// @@ -338,6 +350,10 @@ fn do_list_combination(arr: List(a), k: Int, prefix: List(a)) -> List(List(a)) { /// [2, 3, 1], /// [3, 2, 1], /// ])) +/// +/// [1.0, 1.0] +/// |> combinatorics.list_permutation() +/// |> should.equal([[1.0, 1.0], [1.0, 1.0]]) /// } ///
/// @@ -350,30 +366,16 @@ fn do_list_combination(arr: List(a), k: Int, prefix: List(a)) -> List(List(a)) { pub fn list_permutation(arr: List(a)) -> List(List(a)) { case arr { [] -> [[]] - _ -> - flat_map( - arr, - fn(x) { - let remaining = list.filter(arr, fn(y) { x != y }) - list.map(list_permutation(remaining), fn(perm) { [x, ..perm] }) - }, - ) + _ -> { + use x <- list.flat_map(arr) + // `x` is drawn from the list `arr` above, + // so Ok(...) can be safely asserted as the result of `list.pop` below + let assert Ok(#(_, remaining)) = list.pop(arr, fn(y) { x == y }) + list.map(list_permutation(remaining), fn(perm) { [x, ..perm] }) + } } } -/// Flat map function -fn flat_map(list: List(a), f: fn(a) -> List(b)) -> List(b) { - list - |> list.map(f) - |> concat() -} - -/// Concatenate a list of lists -fn concat(lists: List(List(a))) -> List(a) { - lists - |> list.fold([], list.append) -} - ///
/// /// Spot a typo? Open an issue! diff --git a/test/gleam/gleam_community_maths_combinatorics.gleam b/test/gleam/gleam_community_maths_combinatorics.gleam index 62c3b8c..70267ba 100644 --- a/test/gleam/gleam_community_maths_combinatorics.gleam +++ b/test/gleam/gleam_community_maths_combinatorics.gleam @@ -1,5 +1,6 @@ import gleam_community/maths/combinatorics import gleam/set +import gleam/list import gleeunit import gleeunit/should @@ -103,15 +104,22 @@ pub fn list_cartesian_product_test() { } pub fn list_permutation_test() { - // An empty lists returns an empty list + // An empty lists returns one (empty) permutation [] |> combinatorics.list_permutation() |> should.equal([[]]) + // Singleton returns one (singleton) permutation + // Also works regardless of type of list elements + ["a"] + |> combinatorics.list_permutation() + |> should.equal([["a"]]) + // Test with some arbitrary inputs [1, 2] |> combinatorics.list_permutation() - |> should.equal([[1, 2], [2, 1]]) + |> set.from_list() + |> should.equal(set.from_list([[1, 2], [2, 1]])) // Test with some arbitrary inputs [1, 2, 3] @@ -125,6 +133,20 @@ pub fn list_permutation_test() { [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() + |> 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() + |> list.length() + |> should.equal(5040) } pub fn list_combination_test() {