Kotlin 创建n元笛卡尔积的惯用方法(多组参数的组合)
要创建两组参数的所有可能组合并对其执行操作,可以执行以下操作:Kotlin 创建n元笛卡尔积的惯用方法(多组参数的组合),kotlin,set,cartesian-product,idioms,Kotlin,Set,Cartesian Product,Idioms,要创建两组参数的所有可能组合并对其执行操作,可以执行以下操作: setOf(foo, bar, baz).forEach { a -> setOf(0, 1).forEach { b -> /* use a and b */ } } 但是,如果您有(可能有许多)更多的参数,这将很快变成: 您可以使用为循环编写类似的代码,或者以不同的方式编写: val dAction = { d: String -> /* use a, b, c and d *
setOf(foo, bar, baz).forEach { a ->
setOf(0, 1).forEach { b ->
/* use a and b */
}
}
但是,如果您有(可能有许多)更多的参数,这将很快变成:
您可以使用为循环编写类似的代码,或者以不同的方式编写:
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的操作是反写的。无法推断它们的类型规范,因此必须指定它们。与厄运金字塔相比,它是颠倒的。提供可能值的集合的顺序应该无关紧要,但确实如此:您只需要从一组集合中创建任意组合,然而,在这段代码中,每一行都取决于前一行
如果有一种惯用的方式来做类似或理解的事情,那将非常好,在这种方式中,您()可以做如下事情:
{ /* 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元笛卡尔产品,有什么想法吗?我建议使用列表中的。请参见示例:
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(), booleans.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
我已经自己创建了一个解决方案,所以我不必像所建议的那样添加依赖项
我创建了一个函数,可以使用两个或多个任意大小的集合:
fun cartesianProduct(a: Set<*>, b: Set<*>, vararg sets: Set<*>): Set<List<*>> =
(setOf(a, b).plus(sets))
.fold(listOf(listOf<Any?>())) { acc, set ->
acc.flatMap { list -> set.map { element -> list + element } }
}
.toSet()
函数cartesianProduct
返回一组列表。这些列表存在许多问题:
- 任何类型信息都将丢失,因为返回的集包含包含输入集类型并集的列表。这些列表元素的返回类型是
Any?
。该函数返回一个Set
,即Set,下面是使用任意组合器函数执行此操作的另一种方法:
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"))
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
fun产品(xs:Collection,ys:Collection,combiner:(T,U)->V):Collection=
xs.flatMap{x->
ys.map{y->
组合器(x,y)
}
}
操作员乐趣设置。时间(ys:Set):设置=
乘积(this,ys){x,y->
如果设置了(x),则x+y//将保持结果平坦
(x,y)的else集
}.toSet()
操作员乐趣列表。时间(ys:List):列表=
乘积(this,ys){x,y->
如果(x是列表),则x+y//保持结果平坦
else列表(x,y)
}托利斯先生()
//那你就可以了
listOf(1,2,3)*listOf(真、假)
//或
集合(1,2,3)*集合(真,假)
//您还可以使用产品的组合器创建任意结果对象,例如数据类
延迟生成一系列结果的解决方案。它接受列表,但不接受集合
fun产品(vararg iterables:List):Sequence=Sequence{
require(iterables.map{it.size.toLong()}.reduce(Long::times)
val result=ArrayList()
(0到numberOfIterables)。forEach{iterableIndex->
val elementIndex=产品/L剩余[iterableIndex]%L长度[iterableIndex]
结果.添加(iterables[iterableIndex][elementIndex])
}
收益率(result.toList())
}
}
算法的所有学分都归他和他的答案
通过使用char生成5x5 2-d阵列进行测试,在我的2核机器上生成33554432个产品需要约4.4秒。以下是@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 Collection.getCartesianProduct():Set=
如果(isEmpty())清空()
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()
fun cartesianProduct(a:Set,b:Set,vararg Set:Set):Set=
(一组(a、b)加上(组))
.fold(listOf(listOf()){acc,set->
acc.flatMap{list->set.map{element->list+element}
}
.toSet()
很高兴看到Arrow具有此功能。您可以从函数式编程库中看到这一点。我不希望仅仅为了能够做到这一点而添加额外的依赖项。这个答案对于Arrow的用户来说非常好,或者正在寻找使用Arrow的原因。我将等待一个不同的答案,它可能会揭示一些漂亮的Kotlin的问题构造,或者只是一个普通的:不,没有难看的代码就无法完成。;)从Arrow 0.12.0开始,此方法已被弃用,并且它不再适用于Arrow 0.13.0。非常不幸的是,我发现它优雅而简洁。对于这个答案,仍然存在类型差异的问题,因为星形投影不知道或不关心传递给这个函数的集合中的类型。这以后在使用类型时可能会导致问题,因为它们现在有类型Any?
,必须向下转换。^我刚刚要提到这一点,您不仅会丢失类型推断,还会犯类似cartesianProduct(setOf(1,2),setOf(“a”,“b”))的错误。forEach{(a,b,c)->println(“a=$a,b=$b,c=$c”)}
it将很好地编译,但在运行时崩溃。此外,返回的值是
number = 1, maybe = true
number = 1, maybe = false
number = 1, maybe = null
number = 2, maybe = true
number = 2, maybe = false
number = 2, maybe = null
fun <T> Set<List<*>>.map(transform: KFunction<T>) = map { transform.call(*it.toTypedArray()) }
val parametersList: List<Parameters> = cartesianProduct(e, f).map(::Parameters)
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
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()