Я сам создал решение, поэтому мне не нужно добавлять зависимость, как подсказывает ответ Омара .
Я создал функцию, которая принимает любое количество наборовлюбой размер:
fun cartesianProduct(vararg sets: Set<*>): Set<List<*>> =
when (sets.size) {
0, 1 -> emptySet()
else -> sets.fold(listOf(listOf<Any?>())) { acc, set ->
acc.flatMap { list -> set.map { element -> list + element } }
}.toSet()
}
Пример:
val a = setOf(1, 2)
val b = setOf(3, 4)
val c = setOf(5)
val d = setOf(6, 7, 8)
val abcd: Set<List<*>> = cartesianProduct(a, b, c, d)
println(abcd)
Вывод:
[[1, 3, 5, 6], [1, 3, 5, 7], [1, 3, 5, 8], [1, 4, 5, 6], [1, 4, 5, 7], [1, 4, 5, 8], [2, 3, 5, 6], [2, 3, 5, 7], [2, 3, 5, 8], [2, 4, 5, 6], [2, 4, 5, 7], [2, 4, 5, 8]]
Функция cartesianProduct
возвращает набор списков.Существует ряд проблем с этими списками:
- Любая информация о типах теряется, поскольку возвращаемый набор содержит списки, которые содержат объединение типов входных наборов.Тип возвращаемых элементов этих списков
Any?
.Функция возвращает Set<List<*>>
, то есть Set<List<Any?>>
. - Списки могут быть любого размера, в то время как вы хотите, чтобы они были указанного размера (количество входных наборов, 4 в примере выше).
Однако, используя отражение, мы можем решить эти проблемы.Действие, которое мы хотим выполнить с каждым списком, можно записать как функцию (например, конструктор некоторого класса, который также является просто функцией):
data class Parameters(val number: Int, val maybe: Boolean?) {
override fun toString() = "number = $number, maybe = $maybe"
}
val e: Set<Int> = setOf(1, 2)
val f: Set<Boolean?> = setOf(true, false, null)
val parametersList: List<Parameters> = cartesianProduct(e, f).map { ::Parameters.call(*it.toTypedArray()) }
println(parametersList.joinToString("\n"))
Вывод:
number = 1, maybe = true
number = 1, maybe = false
number = 1, maybe = null
number = 2, maybe = true
number = 2, maybe = false
number = 2, maybe = null
Подпись преобразования (::Parameters
в примере) указывает контракт для содержимого списков.
Поскольку map { ::Parameters.call(*it.toTypedArray()) }
не очень хорошо, я создал вторую функцию расширения, которая делает это дляme:
fun <T> Set<List<*>>.map(transform: KFunction<T>) = map { transform.call(*it.toTypedArray()) }
При этом код становится совершенно идиоматичным:
val parametersList: List<Parameters> = cartesianProduct(e, f).map(::Parameters)
Код доступен с этого GitHub Gist , где я буду обновлять его, еслиЯ когда-либо улучшал это.Существуют также тесты: декартово произведение без входов или с одним входом возвращает пустое множество , как математически ожидается .Я не говорю, что это оптимальное решение или что оно математически обоснованно (не каждое математическое свойство явно реализовано и проверено), но оно работает для цели вопроса.