Skip to content

Commit

Permalink
Adding support for partial permutations
Browse files Browse the repository at this point in the history
Adding length parameter (defaults to list size) to the permutations function, providing the opportunity of creating exhaustives of partial permutations/k-permutations.

Also, changed the assertion of the full permutations test from shouldBe to shouldContainExactlyInAnyOrder, as the order in which the permutations come are not (should not be) relevant. This also makes the test less dependent on the underlying implementation.

This commit does break binary compatibility, due to adding the length parameter.
  • Loading branch information
mickeelm committed May 8, 2023
1 parent 4d11dbf commit dee0f13
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ fun <A> Exhaustive.Companion.collection(collection: Collection<A>): Exhaustive<A
}


fun <A> Exhaustive.Companion.permutations(list: List<A>): Exhaustive<List<A>> {
fun <A> Exhaustive.Companion.permutations(list: List<A>, length: Int = list.size): Exhaustive<List<A>> {
require(list.isNotEmpty()) { "Can't build an Exhaustive for an empty list." }
require(length > 0) { "length must be greater than zero." }
require(length <= list.size) { "length cannot be greater than the list size." }

fun perms(list: List<A>): List<List<A>> = when {
list.isEmpty() -> emptyList()
list.size == 1 -> listOf(list)
else -> {
val result = mutableListOf<List<A>>()
for (i in list.indices) {
perms(list - list[i]).forEach { result.add(it + list[i]) }
fun perms(list: List<A>, length: Int): List<List<A>> = mutableListOf<List<A>>().apply {
for (i in list.indices) {
when(length) {
1 -> add(listOf(list[i]))
else -> perms(list - list[i], length - 1).forEach { add(it + list[i]) }
}
result.toList()
}
toList()
}

return perms(list).exhaustive()
return perms(list, length).exhaustive()
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
package com.sksamuel.kotest.property.exhaustive

import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import io.kotest.property.Exhaustive
import io.kotest.property.exhaustive.permutations

class PermutationsTest : FunSpec() {
init {
test("Exhaustive.permutations") {
Exhaustive.permutations(listOf(1, 2, 3)).values shouldBe listOf(
test("Exhaustive.permutations should generate full permutations when length is omitted") {
Exhaustive.permutations(listOf(1, 2, 3)).values shouldContainExactlyInAnyOrder listOf(
listOf(3, 2, 1), listOf(2, 3, 1), listOf(3, 1, 2), listOf(1, 3, 2), listOf(2, 1, 3), listOf(1, 2, 3)
)
}

test("Exhaustive.permutations should generate partial permutations of given length") {
Exhaustive.permutations(listOf(1, 2, 3), 2).values shouldContainExactlyInAnyOrder listOf(
listOf(1, 2), listOf(1, 3), listOf(2, 1), listOf(2, 3), listOf(3, 1), listOf(3, 2)
)
}

test("Exhaustive.permutations should throw if list is empty") {
shouldThrowWithMessage<IllegalArgumentException>("Can't build an Exhaustive for an empty list.")
{ Exhaustive.permutations(listOf<Nothing>()) }
}

test("Exhaustive.permutations should throw if length is zero") {
shouldThrowWithMessage<IllegalArgumentException>("length must be greater than zero.")
{ Exhaustive.permutations(listOf(1), 0) }
}

test("Exhaustive.permutations should throw if length is negative") {
shouldThrowWithMessage<IllegalArgumentException>("length must be greater than zero.")
{ Exhaustive.permutations(listOf(1), -1) }
}

test("Exhaustive.permutations should throw if length is greater than list length") {
shouldThrowWithMessage<IllegalArgumentException>("length cannot be greater than the list size.")
{ Exhaustive.permutations(listOf(1), 2) }
}
}
}

0 comments on commit dee0f13

Please sign in to comment.