Thomas
01/17/2024, 2:46 AMfun <A, B, R> Collection<A>.combine(other: Collection<B>, transformation:(A, B)->R): List<R> =
ArrayList<R>(this.size * other.size).also{
for(a in this){
for(b in other){
it.add(transformation(a, b))
}
}
}
Example:
fun main(){
println(
listOf(1,2,3).combine(listOf("a", "b", "c")){a, b -> "$a-$b"}
)
}
Output:
[1-a, 1-b, 1-c, 2-a, 2-b, 2-c, 3-a, 3-b, 3-c]
ephemient
01/17/2024, 2:56 AMfun <A, B, R> Iterable<A>.cartesianProduct(other: Iterable<B>, transform: (A, B) -> R): Iterable<R> =
this.flatMap { a -> other.map { b -> transform(a, b) } }
Thomas
01/17/2024, 3:01 AMflatMap
, for me it is like using FP for the sake of using FP.
A nested for loop is dead simple, to the point, with guaranteed good performance π€
Implementing the extension function on Iterable
seems like a good idea too but since Iterables
has no size it might involve resizing the result listThomas
01/17/2024, 3:07 AMtransform
, which follows the Kotlin convention, its a nice touch tooephemient
01/17/2024, 3:23 AMbuildList
instead of ArrayList
fun <A, B, R> Iterable<A>.cartesianProduct(other: Iterable<B>, transform: (A, B) -> R): List<R> =
buildList(if (this is Collection<*> && other is Collection<*>) this.size * other.size else 0) {
for (a in this@cartesianProduct) other.mapTo(this) { b -> f(a, b) }
}
Thomas
01/17/2024, 3:25 AMArrayList
may I ask?ephemient
01/17/2024, 3:25 AMDaniel Pitts
01/17/2024, 3:33 AMflatMap
. As for the intermediate allocations, it usually shouldn't be too bad, as the array sizing is amortized to O(1)
average time.Thomas
01/17/2024, 3:38 AMflatMap
+ map
approach.
To say that something is "not too bad" is to imply that it is not optimal, while I don't see anything to optimize with the nested for loop approach.Daniel Pitts
01/17/2024, 3:41 AMfor
loops less clear than map
or even forEach
, but that might be more of a taste thing. I also spend more time trying to make sure my code is correct and clear, than making sure I micro-optimize a non critical section.Daniel Pitts
01/17/2024, 3:41 AM// cartesian product of two iterables
fun <T, U, R> Iterable<T>.cartesianProduct(other: Iterable<U>, transform: (T, U) -> R): List<R> =
flatMap { t -> other.map { u -> transform(t, u) } }
Daniel Pitts
01/17/2024, 3:44 AMThomas
01/17/2024, 3:44 AMCollections.kt
from stdlib would use for
loops instead of generic higher-order functionsThomas
01/17/2024, 3:45 AMThomas
01/17/2024, 3:48 AMinline fun <T, U, R> Iterable<T>.cartesianProduct(other: Iterable<U>, transform: (T, U) -> R): List<R> =
buildList {
for (t in this@cartesianProduct) {
for (u in other) {
add(transform(t, u))
}
}
}
inline fun <T, U, R> Collection<T>.cartesianProduct(other: Collection<U>, transform: (T, U) -> R): List<R> =
buildList(this.size * other.size) {
for (t in this@cartesianProduct) {
for (u in other) {
add(transform(t, u))
}
}
}
inline fun <T, U> Collection<T>.cartesianProduct(other: Iterable<U>): List<Pair<T, U>> =
this.cartesianProduct(other, ::Pair)
inline fun <T, U> Iterable<T>.cartesianProduct(other: Iterable<U>): List<Pair<T, U>> =
this.cartesianProduct(other, ::Pair)
Thanks again for all the replies!Daniel Pitts
01/17/2024, 3:49 AMThomas
01/17/2024, 3:50 AMThomas
01/17/2024, 3:50 AMDaniel Pitts
01/17/2024, 3:50 AMDaniel Pitts
01/17/2024, 3:51 AMDaniel Pitts
01/17/2024, 3:53 AMfun <T, U, R> Sequence<T>.cartesianProduct(other: Sequence<U>, transform: (T, U) -> R): Sequence<R> =
sequence {
for (t in this@cartesianProduct) {
for (u in other) {
yield(transform(t, u))
}
}
}
fun <T, U> Sequence<T>.cartesianProduct(other: Sequence<U>): Sequence<Pair<T, U>> =
cartesianProduct(other) { t, u -> t to u }
fun <T, U, R> Iterable<T>.cartesianProduct(other: Iterable<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other.asSequence(), transform)
fun <T, U> Iterable<T>.cartesianProduct(other: Iterable<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other.asSequence())
fun <T, U, R> Array<T>.cartesianProduct(other: Array<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other.asSequence(), transform)
fun <T, U> Array<T>.cartesianProduct(other: Array<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other.asSequence())
fun <T, U, R> Sequence<T>.cartesianProduct(other: Iterable<U>, transform: (T, U) -> R): Sequence<R> =
cartesianProduct(other.asSequence(), transform)
fun <T, U> Sequence<T>.cartesianProduct(other: Iterable<U>): Sequence<Pair<T, U>> =
cartesianProduct(other.asSequence())
fun <T, U, R> Iterable<T>.cartesianProduct(other: Sequence<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other, transform)
fun <T, U> Iterable<T>.cartesianProduct(other: Sequence<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other)
fun <T, U, R> Array<T>.cartesianProduct(other: Iterable<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other.asSequence(), transform)
fun <T, U> Array<T>.cartesianProduct(other: Iterable<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other.asSequence())
fun <T, U, R> Sequence<T>.cartesianProduct(other: Array<U>, transform: (T, U) -> R): Sequence<R> =
cartesianProduct(other.asSequence(), transform)
fun <T, U> Sequence<T>.cartesianProduct(other: Array<U>): Sequence<Pair<T, U>> =
cartesianProduct(other.asSequence())
fun <T, U, R> Iterable<T>.cartesianProduct(other: Array<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other.asSequence(), transform)
fun <T, U> Iterable<T>.cartesianProduct(other: Array<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other.asSequence())
fun <T, U, R> Array<T>.cartesianProduct(other: Sequence<U>, transform: (T, U) -> R): Sequence<R> =
asSequence().cartesianProduct(other, transform)
fun <T, U> Array<T>.cartesianProduct(other: Sequence<U>): Sequence<Pair<T, U>> =
asSequence().cartesianProduct(other)
Thomas
01/17/2024, 3:54 AMThomas
01/17/2024, 3:54 AMDaniel Pitts
01/17/2024, 3:54 AMThomas
01/17/2024, 3:56 AMasSequence()
calls hahaDaniel Pitts
01/17/2024, 3:57 AMDaniel Pitts
01/17/2024, 3:57 AMDaniel Pitts
01/17/2024, 4:00 AMephemient
01/17/2024, 4:13 AMKlitos Kyriacou
01/17/2024, 8:45 AMIterable
and Collection
, with the latter using a more efficient way to build the list by avoiding reallocations. This is not ideal. An object can be Iterable
at compile time and Collection
at runtime. For example:
listOf(1, 2, 3).asIterable().cartesianProduct(listOf('a', 'b').asIterable())
This would make the compiler call the Iterable
version of your function, even though its arguments are actually Collection
. It might be better to check the type at runtime (if (this is Collection<*>)
) even though that check itself incurs a slight runtime overhead.Thomas
01/17/2024, 8:53 AMasIterable()
, they must have made a decision and accepted that information will be lost.
This is because calling asIterable()
from a List
is clearly an upcasting action, losing information from upcasting is inevitable π€