Как суммировать список <> массивов

У меня есть список < int [] > myList, где я знаю, что все массивы int [] имеют одинаковую длину - ради аргумента, скажем, у меня есть 500 массивов, каждый из которых имеет длину 2048 элементов. Я хотел бы суммировать все 500 из этих массивов, чтобы дать мне один массив, длиной 2048 элементов, где каждый элемент является суммой всех одинаковых позиций во всех других массивах.

Очевидно, что это тривиально в императивном коде:

int[] sums = new int[myList[0].Length];
foreach(int[] array in myList)
{
    for(int i = 0; i < sums.Length; i++)
    {
        sums[i] += array[i];
    }
}

Но мне было интересно, есть ли хорошая техника Linq или Enumerable.xxx?

Ответ 1

Изменить: Ой... Это стало немного сложнее, пока я не смотрел. Изменение требований может быть реальной PITA.

Итак, возьмите каждую позицию в массиве и суммируйте ее:

var sums = Enumerable.Range(0, myList[0].Length)
           .Select(i => myList.Select(
                     nums => nums[i]
                  ).Sum()
           );

Такой уродливый... но я думаю, что версия заявления будет еще хуже.

Ответ 2

EDIT: Я оставил это здесь ради интереса, но принятый ответ намного приятнее.

EDIT: Хорошо, моя предыдущая попытка (см. историю изменений) была в основном совершенно неправильной...

Вы можете сделать это с помощью одной строки LINQ, но это ужасно:

var results = myList.SelectMany(array => array.Select(
                                               (value, index) => new { value, index })
                    .Aggregate(new int[myList[0].Length],
                               (result, item) => { result[item.index] += value; return result; });

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

Шаг Aggregate полностью не чист - он изменяет свой аккумулятор, когда он идет, добавляя правильное значение в нужную точку.

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

Ответ 3

Это работает с любыми 2 последовательностями, а не только с массивами:

var myList = new List<int[]>
{
    new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 },
    new int[] { 10, 20, 30, 40, 50, 60, 70, 80, 90 }
};

var sums =
    from array in myList
    from valueIndex in array.Select((value, index) => new { Value = value, Index = index })
    group valueIndex by valueIndex.Index into indexGroups
    select indexGroups.Select(indexGroup => indexGroup.Value).Sum()

foreach(var sum in sums)
{
    Console.WriteLine(sum);
}

// Prints:
//
// 11
// 22
// 33
// 44
// 55
// 66
// 77
// 88
// 99

Ответ 4

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

int[] sums = 
    Enumerable.Range(0, listOfArrays[0].Length-1).
        Select(sumTotal => 
            Enumerable.Range(0, listOfArrays.Count-1).
                Aggregate((total, listIndex) => 
                    total += listOfArrays[listIndex][sumTotal])).ToArray();

EDIT - D'oh. По какой-то причине. Сначала меня уклонились. Это немного лучше. Это небольшой взлом, потому что sumTotal действует как вход (позиция в массиве, который используется в вызове Aggregate), и выходная сумма в результирующем IEnumerable, который является интуитивно понятным.

Честно говоря, это гораздо более ужасно, чем это старомодно: -)

Ответ 5

Вот пример, который упрощает работу с инструкцией Linq с производительностью.

var colSums = 
   from col in array.Pivot()
   select col.Sum();

 public static class LinqExtensions {
    public static IEnumerable<IEnumerable<T>> Pivot<T>( this IList<T[]> array ) {
        for( int c = 0; c < array[ 0 ].Length; c++ )
            yield return PivotColumn( array, c );
    }
    private static IEnumerable<T> PivotColumn<T>( IList<T[]> array, int c ) {
        for( int r = 0; r < array.Count; r++ )
            yield return array[ r ][ c ];
    }
}

Ответ 6

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

var result = xs.Aggregate(
    (a, b) => Enumerable.Range(0, a.Length).Select(i => a[i] + b[i]).ToArray()
);

Ответ 7

Это можно сделать с помощью Zip и Aggregate. Вопрос настолько старый, что, вероятно, Зипа не было в то время. Во всяком случае, вот моя версия, надеясь, что это кому-то поможет.

List<int[]> myListOfIntArrays = PopulateListOfArraysOf100Ints();
int[] totals = new int[100];
int[] allArraysSum = myListOfIntArrays.Aggregate(
    totals,
    (arrCumul, arrItem) => arrCumul.Zip(arrItem, (a, b) => a + b))
    .ToArray();