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 π€