Reputation: 4353
I have multiple lists, for the sake of simplicity, let's say 2:
val names = listOf("John", "Tom")
val days = listOf(1, 2, 3)
And I want to iterate over all the combinations of these possible values with a function f: (String, Int): Unit
:
f("John", 1)
f("John", 2)
f("John", 3)
f("Tom", 1)
f("Tom", 2)
f("Tom", 3)
I'd like to find out the idiomatic way to do this in kotlin. Off the top of my head, I imagine that this can be done with a nested map. Something along the lines of:
a.map { itA ->
b.map { itB ->
f(itA, itB)
}
}
EDIT: This doesn't seem to work for me though as it returns:
[() -> kotlin.collections.List<kotlin.Unit>, () -> kotlin.collections.List<kotlin.Unit>]
Upvotes: 12
Views: 13696
Reputation: 4524
You can generate different combinations via flat mapping.
val names = listOf("John", "Tom")
val days = listOf(1, 2, 3)
val isSht = listOf(true, false)
val combz =
names.flatMap { name ->
days.flatMap { day ->
isSht.map { isSht -> "${day} ${isSht}" }
}.map { daySht -> "${daySht} ${name}" }
}.toList()
Upvotes: 0
Reputation: 1322
The name of the output you're looking for is a Cartesian Product.
If you don't need all of the combinations up front, and you're just searching for one or more, you could make it a lazily generated sequence:
private fun <A, B> lazyCartesianProduct(
listA: Iterable<A>,
listB: Iterable<B>
): Sequence<Pair<A, B>> =
sequence {
listA.forEach { a ->
listB.forEach { b ->
yield(a to b)
}
}
}
Then you can map, filter etc only what you need:
val names = listOf("John", "Tom")
val days = listOf(1, 2, 3)
lazyCartesianProduct(names, days)
.map { it.toSomethingElse() }
.find { it.isWhatImLookingFor() }
Upvotes: 13
Reputation: 46480
In case you actually need the result from f
here's what you can do:
fun <T1, T2, R> combine(
first: Iterable<T1>,
second: Iterable<T2>,
combiner: (T1, T2) -> R
): List<R> = first.flatMap { firstItem -> second.map { secondItem -> combiner(firstItem, secondItem) } }
@Test fun test() {
val names = listOf("John", "Tom")
val days = listOf(1, 2, 3)
val result = combine(days, names, { day, name -> "${day}. ${name}" })
@Suppress("DEPRECATION")
kotlin.test.assert(result) {
shouldBe(listOf(
"1. John",
"1. Tom",
"2. John",
"2. Tom",
"3. John",
"3. Tom"
))
}
}
Note: it also works with println
, in that case R
is Unit
.
Upvotes: 4
Reputation: 5983
What's wrong with a regular for
loop?
for (name in names) for (day in days) f(name, day)
Upvotes: 5
Reputation: 13793
The forEach
is the closest as it can get:
names.forEach {
i -> days.forEach { j -> f(i, j)}
}
Example:
private fun f(i: String, j: Int) = println("$i,$j")
Result:
John,1
John,2
John,3
Tom,1
Tom,2
Tom,3
Upvotes: 6