Skip to content

Commit

Permalink
Merge pull request #9 from DanielSherlock/permutations
Browse files Browse the repository at this point in the history
Fixed and tidied `combinatorics.list_permutation` function
  • Loading branch information
NicklasXYZ committed Nov 27, 2023
2 parents bdfa773 + d696734 commit ad11b96
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 23 deletions.
44 changes: 23 additions & 21 deletions src/gleam_community/maths/combinatorics.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
/// <details>
/// <summary>Example:</summary>
///
Expand All @@ -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]])
/// }
/// </details>
///
Expand All @@ -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)
}

/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
Expand Down
26 changes: 24 additions & 2 deletions test/gleam/gleam_community_maths_combinatorics.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gleam_community/maths/combinatorics
import gleam/set
import gleam/list
import gleeunit
import gleeunit/should

Expand Down Expand Up @@ -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]
Expand All @@ -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() {
Expand Down

0 comments on commit ad11b96

Please sign in to comment.