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() {