Kotlin объединяет N списков, суммируя каждый элемент - PullRequest
1 голос
/ 19 октября 2019

Я пытаюсь объединить N списков, суммируя каждый отдельный элемент и выводя один окончательный список с результатом. Вот визуализация:

List 1: { 1, 2, 3 }
List 2: { 4, 5, 6 }
List 3: { 7, 8, 9 }
...
List N: { X, X, X }

Если мы скомбинируем первые три, результат будет:

List Result: { 12, 15, 18 }

Я хотел бы продолжить суммирование элементов для каждого индекса,чисто, встроенный, с оптимальной производительностью.

Я знаю, что здесь поможет zip-оператор (достаточно просто сделать list1.zip (list2) и добавить pair.first и pair.second), но это не такя не могу обрабатывать более одного списка.

Я изучил операции сбора Kotlin, чтобы попытаться найти решение - я изучил использование сложения, уменьшения, flatMap, zip, groupBy и, возможно, преобразования списка в последовательностьвоспользоваться аспектом каждого элемента. Но я не могу найти чистый способ объединить эти операции для получения удовлетворительного результата.

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

Теперь у меня есть метод расширения, называемый padStart (скрытый для краткости). ), чтобы убедиться, что все мои вложенные списки имеют одинаковую длину, а затем я неуклюже создаю временный список, к которому я добавляю значения при итерации:

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
    }

Это решение работает, но сложночитать и не очень чисто. Я ищу лучший!

Ответы [ 4 ]

2 голосов
/ 19 октября 2019

Функционально и прямолинейно:

lists.maxBy { it.size }!!.indices
     .map { index ->
        lists.mapNotNull { it.getOrNull(index) }.sum()
     }

Если вас беспокоит производительность, компилятор все равно ее оптимизирует.

РЕДАКТИРОВАТЬ:

Если числосписки очень большие или значения выбираются службой, вы можете использовать сопрограммы внутри операции map.

1 голос
/ 19 октября 2019

Как вы заметили, zip хорошо справляется с этой работой для двух списков. Если мы используем fold, то мы можем использовать zip, чтобы объединить каждый список со значением аккумулятора и повторить это для каждого списка во вложенном списке.

Если мы можем предположить, что все списки являются тремя элементами(согласно первоначальному вопросу) мы можем сделать что-то вроде этого: (заявление об отказе: минимально проверено, используйте на свой страх и риск :))

fun sumList(xs: List<List<Int>>): List<Int> =
        xs.fold(listOf(0, 0, 0)) { x, y ->
            x.zip(y, Int::plus)
        }

Для переменной длины мы можем дополнить списки или создать специализированныйzip функция, которая обрабатывает int списки различной длины (тот же отказ от ответственности:)):

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))
}

С точки зрения производительности вам, вероятно, будет лучше с изменяемым списком и простыми старыми циклами для увеличения каждого значения, ноЯ думаю, что fold и zip удовлетворяют требованию чистоты. :)

1 голос
/ 19 октября 2019

".. а потом я неуклюже создаю временный список ..." это не коряво

https://en.wikipedia.org/wiki/KISS_principle в этом случае просто "будь проще, глупый"

 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))

}
1 голос
/ 19 октября 2019

Я бы сделал что-то вроде следующего,

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
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...