0xMatthewGroves
0xMatthewGroves

Reputation: 3229

Kotlin combining 'N' lists together by summing each element

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

Answers (4)

Neo
Neo

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

David Tchepak
David Tchepak

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

Naor Tedgi
Naor Tedgi

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

Roaim
Roaim

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

Related Questions