要创建两组参数的所有可能组合并对其执行操作,您可以执行以下操作:
setOf(foo, bar, baz).forEach { a ->
setOf(0, 1).forEach { b ->
/* use a and b */
}
}
但是,如果您有更多(可能更多)参数,则很快就会变成pyramid of doom:
setOf(foo, bar, baz).forEach { a ->
setOf(0, 1).forEach { b ->
setOf(true, false, null).forEach { c ->
setOf("Hello,", "World!").forEach { d ->
/* use a, b, c and d */
}
}
}
}
您可以使用for
循环类似地编写此代码,也可以这样编写:
val dAction = { d: String -> /* use a, b, c and d */ }
val cAction = { c: Boolean? -> setOf("Hello,", "World!").forEach(dAction) }
val bAction = { b: Int -> setOf(true, false, null).forEach(cAction) }
val aAction = { a: Any? -> setOf(0, 1).forEach(bAction) }
setOf(foo, bar, baz).forEach(aAction)
但是我认为这没有什么更好,因为这里存在一些可读性问题:d,c,b和a的动作是相反编写的。无法推断出它们的类型规格,因此必须指定它们。与厄运金字塔相比,它被顺序颠倒了。提供可能值的集合的顺序无关紧要,但是,它确实如此:您只想从一堆集合中创建任何组合,但是,在此代码中,每一行都取决于前一行。
有一种惯用的方式来进行类似Python's或Haskell's的理解,您(almost like the mathematical notation)可以进行类似的事情
{ /* use a, b, c and d */
for a in setOf(foo, bar, baz),
for b in setOf(0, 1),
for c in setOf(true, false, null),
for d in setOf("Hello,", "World!")
}
这很容易阅读:没有过多的缩进,首先是您感兴趣的操作,数据源的定义非常明确,等等。
旁注:flatMap
-flatMap
-...- flatMap
-map
也会发生类似的问题。
关于如何在Kotlin中巧妙地创建n元笛卡尔积的任何想法?
答案 0 :(得分:1)
我建议在List
上使用Arrow-kt
的Applicative。参见示例:
val ints = listOf(1, 2, 3, 4)
val strings = listOf("a", "b", "c")
val booleans = listOf(true, false)
val combined = ListK.applicative()
.tupled(ints.k(), strings.k(), doubles.k())
.fix()
// or use the shortcut `arrow.instances.list.applicative.tupled`
// val combined = tupled(ints, strings, booleans)
combined.forEach { (a, b, c) -> println("a=$a, b=$b, c=$c") }
哪个生产笛卡尔积
a = 1,b = a,c = true
a = 1,b = b,c = true
a = 1,b = c,c = true
a = 2,b = a,c = true
a = 2,b = b,c = true
a = 2,b = c,c = true
a = 3,b = a,c = true
a = 3,b = b,c = true
a = 3,b = c,c = true
a = 4,b = a,c = true
a = 4,b = b,c = true
a = 4,b = c,c = true
a = 1,b = a,c = false
a = 1,b = b,c = false
a = 1,b = c,c = false
a = 2,b = a,c = false
a = 2,b = b,c = false
a = 2,b = c,c = false
a = 3,b = a,c = false
a = 3,b = b,c = false
a = 3,b = c,c = false
a = 4,b = a,c = false
a = 4,b = b,c = false
a = 4,b = c,c = false
答案 1 :(得分:1)
我自己创建了一个解决方案,因此不必像Omar's answer那样添加依赖项。
我创建了一个函数,该函数接受任意数量的任意大小的集合:
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?>>
。但是,使用反射,我们可以解决这些问题。我们希望对每个列表执行的操作可以编写为一个函数(例如,某个类的构造函数,它也只是一个函数):
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()) }
不太好,所以我创建了第二个扩展函数来为我做这件事:
fun <T> Set<List<*>>.map(transform: KFunction<T>) = map { transform.call(*it.toTypedArray()) }
这样,代码就变得很惯用了:
val parametersList: List<Parameters> = cartesianProduct(e, f).map(::Parameters)
该代码可从this GitHub Gist获得,如果我进行了改进,我将在此处对其进行更新。还有测试:无输入或单输入的笛卡尔积返回空集as is mathematically expected。我并不是说这是一个最佳解决方案,也不是说它在数学上是合理的(并不是每个数学属性都得到明确实现和测试),但是它可以解决问题。
答案 2 :(得分:1)
这是@Erik的答案的改编版本,它维护类型安全性并可以在功能链中使用:
fun <T> Collection<Iterable<T>>.getCartesianProduct(): Set<List<T>> =
if (isEmpty()) emptySet()
else drop(1).fold(first().map(::listOf)) { acc, iterable ->
acc.flatMap { list -> iterable.map(list::plus) }
}.toSet()
如果我们要保持输入大小至少为两个的要求,这就是我重写该解决方案的类型安全的方法:
fun <T> cartesianProduct(a: Set<T>, b: Set<T>, vararg sets: Set<T>): Set<List<T>> =
(setOf(a, b).plus(sets))
.fold(listOf(listOf<T>())) { acc, set ->
acc.flatMap { list -> set.map { element -> list + element } }
}
.toSet()
答案 3 :(得分:0)
这是使用任意组合器功能的另一种方法:
fun <T, U, V> product(xs: Collection<T>, ys: Collection<U>, combiner: (T, U) -> V): Collection<V> =
xs.flatMap { x ->
ys.map { y ->
combiner(x, y)
}
}
operator fun <T, U> Set<T>.times(ys: Set<U>): Set<Set<*>> =
product(this, ys) { x, y ->
if (x is Set<*>) x + y // keeps the result flat
else setOf(x, y)
}.toSet()
operator fun <T, U> List<T>.times(ys: List<U>): List<List<*>> =
product(this, ys) { x, y ->
if (x is List<*>) x + y // keeps the result flat
else listOf(x, y)
}.toList()
// then you can do
listOf(1, 2, 3) * listOf(true, false)
// or
setOf(1, 2, 3) * setOf(true, false)
// you can also use product's combiner to create arbitrary result objects, e.g. data classes
答案 4 :(得分:0)
懒惰地产生结果序列的解决方案。它需要列表,但没有设置。
fun <T> product(vararg iterables: List<T>): Sequence<List<T>> = sequence {
require(iterables.map { it.size.toLong() }.reduce(Long::times) <= Int.MAX_VALUE) {
"Cartesian product function can produce result whose size does not exceed Int.MAX_VALUE"
}
val numberOfIterables = iterables.size
val lstLengths = ArrayList<Int>()
val lstRemaining = ArrayList(listOf(1))
iterables.reversed().forEach {
lstLengths.add(0, it.size)
lstRemaining.add(0, it.size * lstRemaining[0])
}
val nProducts = lstRemaining.removeAt(0)
(0 until nProducts).forEach { product ->
val result = ArrayList<T>()
(0 until numberOfIterables).forEach { iterableIndex ->
val elementIndex = product / lstRemaining[iterableIndex] % lstLengths[iterableIndex]
result.add(iterables[iterableIndex][elementIndex])
}
yield(result.toList())
}
}
该算法的所有功劳归于Per L,答案是here. 在我的2核计算机上进行了用char生成5x5 2-d数组的测试,大约需要4.4秒才能生成33554432产品。