Использование LINQ, чтобы найти совокупную сумму массива чисел в C#

StackOverflow https://stackoverflow.com/questions/4823467

Вопрос

У меня есть строка CSV, содержащая удвоение (например, «0,3,0,4,0,3»), и я хочу иметь возможность вывести двойной массив, содержащий совокупную сумму этих чисел (например, [0,3,0,7,1,0]).

Пока что у меня есть

double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();

что дает числа как массив, но не совокупную сумму чисел.

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

Это было полезно?

Решение

var input=new double[]{ ... }
double sum=0;

var output=input
    .Select(w=>sum+=w);

Другие советы

Есть время для общности, и есть время для решения проблемы. Это одно из последних времен. Если вы хотите сделать метод, который превращает последовательность удвоения в последовательность частичных сумм, то просто сделайте это:

public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence)
{
    double sum = 0;
    foreach(var item in sequence)
    {
        sum += item;
        yield return sum;
    }        
}

Легкий. Нет никакого путаницы с агрегатами и сложными запросами и еще много чего. Легко понять, простой в отладении, простой в использовании:

textBox_f.Text
    .Split(new char[]{','})
    .Select(s => double.Parse(s))
    .CumulativeSum()
    .ToArray();

Теперь я отмечаю, что если это пользовательский ввод, то Double.parse может бросить исключение; Это может быть лучшей идеей сделать что -то вроде:

public static double? MyParseDouble(this string s)
{
    double d;
    if (double.TryParse(s, out d))
        return d;
    return null;
}

public static IEnumerable<double?> CumulativeSum(this IEnumerable<double?> sequence)
{
    double? sum = 0;
    foreach(var item in sequence)
    {
        sum += item;
        yield return sum;
    }        
}
...
textBox_f.Text
    .Split(new char[]{','})
    .Select(s => s.MyParseDouble())
    .CumulativeSum()
    .ToArray();

И теперь вы не получите исключение, если пользователь совершает ошибку печати; Вы получаете нуль.

У меня было аналогичное требование некоторое время назад. По сути, мне нужно было сделать агрегацию, но мне также нужно было выбрать каждое промежуточное значение. Итак, я написал метод расширения с именем SelectAggregate (Вероятно, не самое подходящее имя, но тогда я не смог найти ничего лучше), которое можно использовать таким образом:

double[] numbers = new [] { 0.3, 0.4, 0.3 };
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray();

Вот код:

    public static IEnumerable<TAccumulate> SelectAggregate<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        source.CheckArgumentNull("source");
        func.CheckArgumentNull("func");
        return source.SelectAggregateIterator(seed, func);
    }

    private static IEnumerable<TAccumulate> SelectAggregateIterator<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        TAccumulate previous = seed;
        foreach (var item in source)
        {
            TAccumulate result = func(previous, item);
            previous = result;
            yield return result;
        }
    }

Вы хотите использовать Aggregate оператор, с List<double> как аккумулятор агрегации. Таким образом, вы можете создать проекцию, которая сама по себе является последовательности сумм.

Вот пример, чтобы начать вас:

double[] runningTotal = textBox_f.Text
            .Split(new char[]{','})
            .Select(s => double.Parse(s))
            .Aggregate((IEnumerable<double>)new List<double>(), 
                       (a,i) => a.Concat(new[]{a.LastOrDefault() + i}))
            .ToArray();

Почему это должно быть LINQ?

var cumulative = new double[probabilities.Length];
for (int i = 0; i < probabilities.Length; i++)
    cumulative[i] = probabilities[i] + (i == 0 ? 0 : cumulative[i-1]);

Прежде всего, я не думаю, что это хорошая задача для LINQ. Простой старый foreach сделает это лучше. Но как головоломка это нормально.

Первая идея заключалась в том, чтобы использовать подраздел, но мне это не нравится, потому что это O (n^2). Вот мое линейное решение:

        double[] probabilities = new double[] { 0.3, 0.4, 0.3};
        probabilities
            .Aggregate(
                new {sum=Enumerable.Empty<double>(), last = 0.0d},
                (a, c) => new {
                    sum = a.sum.Concat(Enumerable.Repeat(a.last+c,1)),
                    last = a.last + c
                },
                a => a.sum
            );

Используйте Rx:

var input=new double[]{ ... }
var output = new List<double>();
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add);

На самом деле это довольно просто для обобщения с использованием генератора. Вот новый метод расширения под названием Accumulate это работает как сочетание Select а также Aggregate. Анкет Он возвращает новую последовательность, применяя бинарную функцию к каждому элементу в последовательности и накопленном значении до сих пор.

 public static class EnumerableHelpers 
 {
    public static IEnumerable<U> Accumulate<T, U>(this IEnumerable<T> self, U init, Func<U, T, U> f) 
    {
        foreach (var x in self)
            yield return init = f(init, x);
    }

    public static IEnumerable<T> Accumulate<T>(this IEnumerable<T> self, Func<T, T, T> f)
    {
        return self.Accumulate(default(T), f);
    }

    public static IEnumerable<double> PartialSums(this IEnumerable<double> self)
    {
        return self.Accumulate((x, y) => x + y);
    }

    public static IEnumerable<int> PartialSums(this IEnumerable<int> self)
    {
        return self.Accumulate((x, y) => x + y);
    }
 }

Вот а способ сделать это с помощью LINQ:

double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 };
var doublesSummed = new List<double>();

Enumerable.Aggregate(doubles, (runningSum, nextFactor) => {
    double currentSum = runningSum + nextFactor;
    doublesSummed.Add(currentSum);
    return currentSum;
});

doublesSummed.Dump();

В Linqpad:

  • 4
  • 5.9
  • 10
  • 12.9

Кумулятивная сумма для List<double>:

var nums = new List<double>() { 0.3, 0.0, 0.4, 1.1 };
var cumsum = nums.Aggregate(new List<double> (), 
              (list, next) => { list.Add(list.LastOrDefault() + next); return list; });
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top