From 788e142466eeb4c195b16e63e77cb60ea6d0adb1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 24 Oct 2023 22:59:16 +0100 Subject: [PATCH 1/3] Fixed and tidied combinatorics.list_permutation function: it no longer mistakenly shortens lists with duplicates --- src/gleam_community/maths/combinatorics.gleam | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index dd1e6b2..112e22c 100644 --- a/src/gleam_community/maths/combinatorics.gleam +++ b/src/gleam_community/maths/combinatorics.gleam @@ -350,30 +350,14 @@ 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) + 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! From 52281ce43165bffb0c2845fbbe68308b432e961e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 19 Nov 2023 22:40:59 +0000 Subject: [PATCH 2/3] Added tests and docs for combinatorics.list_permutation function behaviour on duplicate elements --- src/gleam_community/maths/combinatorics.gleam | 16 ++++++++++++ .../gleam_community_maths_combinatorics.gleam | 26 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index 112e22c..d65bbab 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]]) /// } ///
/// 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() { From d6967344008183c98917990a2f0d15e577776d3b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 19 Nov 2023 22:59:03 +0000 Subject: [PATCH 3/3] Add explanatory comment for safe assert in combinatorics.list_permutation --- src/gleam_community/maths/combinatorics.gleam | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index d65bbab..ee771a1 100644 --- a/src/gleam_community/maths/combinatorics.gleam +++ b/src/gleam_community/maths/combinatorics.gleam @@ -368,6 +368,8 @@ pub fn list_permutation(arr: List(a)) -> List(List(a)) { [] -> [[]] _ -> { 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] }) }