Skip to content

Instantly share code, notes, and snippets.

@erikhuizinga
Last active August 14, 2023 08:24
Show Gist options
  • Save erikhuizinga/d2ca2b501864df219fd7f25e4dd000a4 to your computer and use it in GitHub Desktop.
Save erikhuizinga/d2ca2b501864df219fd7f25e4dd000a4 to your computer and use it in GitHub Desktop.
Kotlin function for cartesian product of any number of sets of any size
import kotlin.reflect.KFunction
typealias CartesianProduct = Set<List<*>>
/**
* Create the cartesian product of any number of sets of any size. Useful for parameterized tests
* to generate a large parameter space with little code. Note that any type information is lost, as
* the returned set contains list of any combination of types in the input set.
*
* @param a The first set.
* @param b The second set.
* @param sets Any additional sets.
*/
fun cartesianProduct(a: Set<*>, b: Set<*>, vararg sets: Set<*>): CartesianProduct =
(setOf(a, b).plus(sets))
.fold(listOf(listOf<Any?>())) { acc, set ->
acc.flatMap { list -> set.map { element -> list + element } }
}
.toSet()
/**
* Transform elements of a cartesian product.
*/
fun <T> CartesianProduct.map(transform: KFunction<T>) = map { transform.call(*it.toTypedArray()) }
// JUnit 4 is a dependency for these imports
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class CartesianProductTest {
private val emptySet: Set<*> = emptySet<Nothing>()
private val a = setOf(1, 2)
private val b = setOf('3', '4')
@Test
fun `Given the empty set, When the cartesian product with itself is created, Then it returns the empty set`() {
assertTrue(cartesianProduct(emptySet, emptySet).isEmpty())
}
@Test
fun `Given nonempty set A, When the cartesian product with the empty set is created, Then it returns the empty set`() {
assertTrue(cartesianProduct(a, emptySet).isEmpty())
assertTrue(cartesianProduct(emptySet, a).isEmpty())
}
@Test
fun `Given different nonempty sets A and B, When A x B and B x A are created, Then they are unequal`() =
assertNotEquals(
cartesianProduct(a, b),
cartesianProduct(b, a)
)
@Test
fun `Given different nonempty sets A, B, C and D, When A x B x C x D is created, Then it returns the correct cartesian product`() {
val c = setOf(5)
val d = setOf("6", "7", "8, 9")
val expected: CartesianProduct = setOf(
listOf(1, '3', 5, "6"),
listOf(1, '3', 5, "7"),
listOf(1, '3', 5, "8, 9"),
listOf(1, '4', 5, "6"),
listOf(1, '4', 5, "7"),
listOf(1, '4', 5, "8, 9"),
listOf(2, '3', 5, "6"),
listOf(2, '3', 5, "7"),
listOf(2, '3', 5, "8, 9"),
listOf(2, '4', 5, "6"),
listOf(2, '4', 5, "7"),
listOf(2, '4', 5, "8, 9")
)
val actual = cartesianProduct(a, b, c, d)
assertEquals(expected, actual)
}
@Test
fun `Given a parameter class and sets of input arguments, When the arguments' cartesian product is created and it is mapped to the parameter class, Then it returns the correct list of parameters`() =
assertEquals(
listOf(
Parameters(1, true),
Parameters(1, false),
Parameters(1, null),
Parameters(2, true),
Parameters(2, false),
Parameters(2, null)
),
cartesianProduct(a, setOf(true, false, null))
.map(::Parameters)
)
internal data class Parameters(val number: Int, val maybe: Boolean?)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment