Skip to content

Commit 5cd94fd

Browse files
authored
fix list.permutation on lists with non unique item values (#358)
1 parent 93c3223 commit 5cd94fd

File tree

3 files changed

+113
-24
lines changed

3 files changed

+113
-24
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
## v0.24.1 - unreleased
44

5+
- Fixed a bug where `list.permutations` would not correctly permutate lists
6+
with non-unique item values.
57
- For `regexp.compile` unicode character properties are now used when
68
resolving `\B`, `\b`, `\D`, `\d`, `\S`, `\s`, `\W`, and `\w` on target
79
Erlang.
810
- `list.sort` is now tail recursive and will no longer exceed the stack size
911
on large inputs on target JavaScript.
10-
- `list.sort` is now a "stable" sort, meaning equal elements are sorted in
11-
the same order that they appear in the input.
12+
- `list.sort` is now a "stable" sort, meaning elements which are equal in
13+
regards to the given comparison function will keep their previous order.
1214
- Added functions `function.apply1` through `function.apply3` which help
1315
working with functions in pipelines.
1416
- Fixed a bug where `regex.scan` would not work correctly on utf8.

src/gleam/list.gleam

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,18 +1443,34 @@ pub fn partition(
14431443
/// [[1, 2], [2, 1]]
14441444
/// ```
14451445
///
1446+
/// Notice, that this function is not tail recursive:
1447+
///
1448+
/// The execution limits on permutations are iterations rather than stack size;
1449+
/// for a permutation with `10` items a non tail recursive algorithm will at
1450+
/// worst built up a stack size of `10` before it goes back to `1` eventually,
1451+
/// while a permutation of say `5_000` items will not compute, in reasonable
1452+
/// time, despite not exceeding stack size.
1453+
///
14461454
pub fn permutations(l: List(a)) -> List(List(a)) {
14471455
case l {
14481456
[] -> [[]]
14491457
_ ->
1450-
map(
1451-
l,
1452-
fn(x) {
1453-
filter(l, fn(y) { y != x })
1454-
|> permutations
1455-
|> map(append([x], _))
1456-
},
1457-
)
1458+
l
1459+
|> index_map(fn(i_idx, i) {
1460+
l
1461+
|> index_fold(
1462+
[],
1463+
fn(acc, j, j_idx) {
1464+
case i_idx == j_idx {
1465+
True -> acc
1466+
False -> [j, ..acc]
1467+
}
1468+
},
1469+
)
1470+
|> reverse
1471+
|> permutations
1472+
|> map(fn(permutation) { [i, ..permutation] })
1473+
})
14581474
|> flatten
14591475
}
14601476
}

test/gleam/list_test.gleam

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -848,33 +848,104 @@ pub fn permutations_test() {
848848
|> list.permutations
849849
|> should.equal([[1, 2], [2, 1]])
850850

851-
let expected = [
851+
[1, 2, 3]
852+
|> list.permutations
853+
|> should.equal([
852854
[1, 2, 3],
853855
[1, 3, 2],
854856
[2, 1, 3],
855857
[2, 3, 1],
856858
[3, 1, 2],
857859
[3, 2, 1],
858-
]
860+
])
859861

860-
[1, 2, 3]
862+
[1, 2, 3, 4]
861863
|> list.permutations
862-
|> should.equal(expected)
864+
|> should.equal([
865+
[1, 2, 3, 4],
866+
[1, 2, 4, 3],
867+
[1, 3, 2, 4],
868+
[1, 3, 4, 2],
869+
[1, 4, 2, 3],
870+
[1, 4, 3, 2],
871+
[2, 1, 3, 4],
872+
[2, 1, 4, 3],
873+
[2, 3, 1, 4],
874+
[2, 3, 4, 1],
875+
[2, 4, 1, 3],
876+
[2, 4, 3, 1],
877+
[3, 1, 2, 4],
878+
[3, 1, 4, 2],
879+
[3, 2, 1, 4],
880+
[3, 2, 4, 1],
881+
[3, 4, 1, 2],
882+
[3, 4, 2, 1],
883+
[4, 1, 2, 3],
884+
[4, 1, 3, 2],
885+
[4, 2, 1, 3],
886+
[4, 2, 3, 1],
887+
[4, 3, 1, 2],
888+
[4, 3, 2, 1],
889+
])
863890

864891
["a", "b"]
865892
|> list.permutations
866893
|> should.equal([["a", "b"], ["b", "a"]])
867894

868-
// TCO test
869-
case recursion_test_cycles > 2 {
870-
// Permutation count:
871-
// 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 = 40_320
872-
// 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 = 362_800
873-
True ->
874-
[1, 2, 3, 4, 5, 6, 7, 8, 9]
875-
|> list.permutations
876-
False -> [[]]
877-
}
895+
[1, 1]
896+
|> list.permutations
897+
|> should.equal([[1, 1], [1, 1]])
898+
899+
[1, 1, 1]
900+
|> list.permutations
901+
|> should.equal([
902+
[1, 1, 1],
903+
[1, 1, 1],
904+
[1, 1, 1],
905+
[1, 1, 1],
906+
[1, 1, 1],
907+
[1, 1, 1],
908+
])
909+
910+
[1, 2, 2]
911+
|> list.permutations
912+
|> should.equal([
913+
[1, 2, 2],
914+
[1, 2, 2],
915+
[2, 1, 2],
916+
[2, 2, 1],
917+
[2, 1, 2],
918+
[2, 2, 1],
919+
])
920+
921+
["a", "a", "a", "a"]
922+
|> list.permutations
923+
|> should.equal([
924+
["a", "a", "a", "a"],
925+
["a", "a", "a", "a"],
926+
["a", "a", "a", "a"],
927+
["a", "a", "a", "a"],
928+
["a", "a", "a", "a"],
929+
["a", "a", "a", "a"],
930+
["a", "a", "a", "a"],
931+
["a", "a", "a", "a"],
932+
["a", "a", "a", "a"],
933+
["a", "a", "a", "a"],
934+
["a", "a", "a", "a"],
935+
["a", "a", "a", "a"],
936+
["a", "a", "a", "a"],
937+
["a", "a", "a", "a"],
938+
["a", "a", "a", "a"],
939+
["a", "a", "a", "a"],
940+
["a", "a", "a", "a"],
941+
["a", "a", "a", "a"],
942+
["a", "a", "a", "a"],
943+
["a", "a", "a", "a"],
944+
["a", "a", "a", "a"],
945+
["a", "a", "a", "a"],
946+
["a", "a", "a", "a"],
947+
["a", "a", "a", "a"],
948+
])
878949
}
879950

880951
pub fn window_test() {

0 commit comments

Comments
 (0)