Reputation: 3229
I'm attempting to combine N lists together by summing each individual element together and outputting one final list with the result. Here's a visualization:
List 1: { 1, 2, 3 }
List 2: { 4, 5, 6 }
List 3: { 7, 8, 9 }
...
List N: { X, X, X }
If we combined the first three, the output would be:
List Result: { 12, 15, 18 }
I'd like to continue summing the elements at each index, cleanly, inline, with optimal performance.
I know the zip operator would help here (easy enough to do list1.zip(list2) and add pair.first and pair.second) but it doesn't handle more than one list.
I educated myself on the Kotlin collection operations to try to find a solution - I looked into using fold, reduce, flatMap, zip, groupBy, and possibly transforming the List into a sequence to take advantage of the each-element aspect. But I can't seem to find a clean way to chain these operations together for a satisfying result.
ADDITIONAL INFO:
What I have now is an extension method called padStart (hidden for brevity) to help ensure all my nested lists are the same length, and then I'm clumsily creating a temporary list that I'm adding values to as I iterate:
myNestedLists
.run {
// Calculate largest list size of 'n' lists
val largestSize = map { it.size }.max() ?: 0
// Pad lists with zeroes if size < largestSize
map { it.padStart(largestSize, 0.0) }
}
.run {
// Create temporary list, add all of the first elements to it
val combinedList: MutableList<Double> = mutableListOf()
combinedList.addAll(this.first())
// Now, for each nested list, add each individual element with the combined value at combinedList[index]
drop(1).forEach {
it.forEachIndexed { index, value ->
combinedList[index] += value
}
}
// Return the final result
combinedList
}
This solution works, but it's hard to read and not very clean. I'm looking for a better one!
Upvotes: 4
Views: 3206
Reputation: 2039
Functional and straight forward:
lists.maxBy { it.size }!!.indices
.map { index ->
lists.mapNotNull { it.getOrNull(index) }.sum()
}
If you're concerned about performance, the compiler will optimise it anyways.
EDIT:
If the number of lists is very big or the values are fetched by a service, you could use coroutines inside the map
operation.
Upvotes: 4
Reputation: 10474
As you observed zip
does this job well for two lists. If we use fold
, then we can use zip
to combine each list with an accumulator value, and repeat this for every list in the nested list.
If we can assume that all lists are three elements (as per the initial question) we can do something like this: (disclaimer: minimally tested, use at your own risk :) )
fun sumList(xs: List<List<Int>>): List<Int> =
xs.fold(listOf(0, 0, 0)) { x, y ->
x.zip(y, Int::plus)
}
For variable length we can pad the lists, or create a specialised zip
function that handles int lists of different lengths (same disclaimer :)):
fun List<Int>.padTo(requiredSize: Int) =
if (size < requiredSize) {
plus(generateSequence { 0 }.take(requiredSize - size))
} else {
this
}
fun sumVariableLists(xs: List<List<Int>>): List<Int> =
xs.fold(emptyList()) { x, y ->
val maxSize = maxOf(x.size, y.size)
x.padTo(maxSize).zip(y.padTo(maxSize), Int::plus)
}
@Test
fun example() {
val x = listOf(
listOf(1, 2, 3, 10),
listOf(4, 5, 6),
listOf(7, 8, 9, 11))
assertEquals(listOf(12, 15, 18, 21), sumVariableLists(x))
}
Performance-wise you are probably be better off with a mutable list and plain old loops to increment each value, but I think the fold
and zip
satisfy the cleanliness requirement. :)
Upvotes: 1
Reputation: 5707
"..and then I'm clumsily creating a temporary list.." its not clumsily
https://en.wikipedia.org/wiki/KISS_principle in this case just "keep it simple, stupid"
fun <T> sumLists(vararg lists: List<T>, sum: (a: T, b: T) -> T): List<T> {
val result = ArrayList<T>(lists.first())
lists.drop(1).forEach {l->
l.forEachIndexed {i,v->
result[i] = sum(result[i], v)
}
}
return result.toList()
}
fun main(args: Array<String>) {
val l1 = listOf(1, 2, 3)
val l2 = listOf(4, 5, 6)
val l3 = listOf(7, 8, 9)
print(sumLists(l1,l2,l3,sum = Int::plus))
}
Upvotes: 1
Reputation: 2358
I would do something like following,
fun getResult(nestedList: List<List<Int>>) : List<Int> {
val list = mutableListOf<Int>()
val maxSize = nestedList.maxBy{it.size}?.size ?: 0
repeat(maxSize) { i ->
val sum = nestedList.sumBy{if(i<it.size) it[i] else 0}
list.add(sum)
}
return list
}
Upvotes: 1