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.

The empty list check was removed, as an empty list will result in a list of an empty list (as with all permutations of length 0).

This commit does break binary compatibility, due to adding the length parameter.
  • Loading branch information
mickeelm committed Jun 1, 2023
1 parent 2661fd2 commit e7610ef
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 14 deletions.
3 changes: 2 additions & 1 deletion kotest-property/api/kotest-property.api
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,8 @@ public final class io/kotest/property/exhaustive/CharKt {

public final class io/kotest/property/exhaustive/CollectionsKt {
public static final fun collection (Lio/kotest/property/Exhaustive$Companion;Ljava/util/Collection;)Lio/kotest/property/Exhaustive;
public static final fun permutations (Lio/kotest/property/Exhaustive$Companion;Ljava/util/List;)Lio/kotest/property/Exhaustive;
public static final fun permutations (Lio/kotest/property/Exhaustive$Companion;Ljava/util/List;I)Lio/kotest/property/Exhaustive;
public static synthetic fun permutations$default (Lio/kotest/property/Exhaustive$Companion;Ljava/util/List;IILjava/lang/Object;)Lio/kotest/property/Exhaustive;
}

public final class io/kotest/property/exhaustive/ExhaustiveKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,17 @@ fun <A> Exhaustive.Companion.collection(collection: Collection<A>): Exhaustive<A
}


fun <A> Exhaustive.Companion.permutations(list: List<A>): Exhaustive<List<A>> {
require(list.isNotEmpty()) { "Can't build an Exhaustive for an empty list." }
fun <A> Exhaustive.Companion.permutations(list: List<A>, length: Int = list.size): Exhaustive<List<A>> {
require(length in 0..list.size) { "length must be between 0 and the list size (${list.size}), but was $length." }

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>> = buildList {
when (length) {
0 -> add(emptyList())
else -> list.forEach { element ->
perms(list - element, length - 1).forEach { add(it + element) }
}
result.toList()
}
}

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

import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import io.kotest.matchers.shouldBe
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 generate a single empty list when length is zero") {
Exhaustive.permutations(listOf(1, 2, 3), 0).values shouldBe listOf( listOf() )
}

test("Exhaustive.permutations should throw if length is negative") {
shouldThrowWithMessage<IllegalArgumentException>("length must be between 0 and the list size (1), but was -1.")
{ Exhaustive.permutations(listOf(1), -1) }
}

test("Exhaustive.permutations should throw if length is greater than list length") {
shouldThrowWithMessage<IllegalArgumentException>("length must be between 0 and the list size (3), but was 7.")
{ Exhaustive.permutations(listOf(1,2,3), 7) }
}
}
}

0 comments on commit e7610ef

Please sign in to comment.