Суммирование предыдущих значений в IEnumerable - PullRequest
16 голосов
/ 01 июля 2011

У меня есть последовательность чисел:

var seq = new List<int> { 1, 3, 12, 19, 33 };

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

{ 1, 3, 12, 19, 33 } --> {1, 4, 16, 35, 68 }

Я придумал следующее, но мне не нравится переменная состояния count.Мне также не нравится тот факт, что я использую значения Enumerable, не воздействуя на него.

int count = 1;
var summed = values.Select(_ => values.Take(count++).Sum());

Как еще это можно сделать?

Ответы [ 7 ]

19 голосов
/ 01 июля 2011

Это общий шаблон в функциональном программировании, который в F # называется scan .Это похоже на C # Enumerable.Aggregate и F # * fold , за исключением того, что он выдает промежуточные результаты аккумулятора вместе с окончательным результатом.Мы можем красиво реализовать сканирование в C # с помощью метода расширения:

public static IEnumerable<U> Scan<T, U>(this IEnumerable<T> input, Func<U, T, U> next, U state) {
    yield return state;
    foreach(var item in input) {
        state = next(state, item);
        yield return state;
    }
}

, а затем использовать его следующим образом:

var seq = new List<int> { 1, 3, 12, 19, 33 };
var transformed = seq.Scan(((state, item) => state + item), 0).Skip(1);
8 голосов
/ 01 июля 2011

«Чистый» LINQ:

var result = seq.Select((a, i) => seq.Take(i + 1).Sum());

Еще один «чистый» LINQ O (n):

var res = Enumerable.Range(0, seq.Count)
    .Select(a => a == 0 ? seq[a] : seq[a] += seq[a - 1]);

Еще один LINQ, с поддержкой состояния:

var tmp = 0;
var result = les.Select(a => { tmp += a; return tmp; });
3 голосов
/ 01 июля 2011
var seq = new List<int> { 1, 3, 12, 19, 33 };

var summed = new List<int>();

seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
1 голос
/ 28 января 2014

Ответ Стивена Свенсена великолепен, сканирование именно то, что вам нужно.Существует еще одна версия сканирования, которая не требует начального числа, что немного больше подходит для вашей конкретной проблемы.

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

Вы можете реализовать эту версию сканирования в C # следующим образом:

public static IEnumerable<T> Scan<T>(this IEnumerable<T> Input, Func<T, T, T> Accumulator)
{
    using (IEnumerator<T> enumerator = Input.GetEnumerator())
    {
        if (!enumerator.MoveNext())
            yield break;
        T state = enumerator.Current;
        yield return state;
        while (enumerator.MoveNext())
        {
            state = Accumulator(state, enumerator.Current);
            yield return state;
        }
    }
}

А затем используйте его следующим образом:

IEnumerable<int> seq = new List<int> { 1, 3, 12, 19, 33 };
IEnumerable<int> transformed = seq.Scan((state, item) => state + item);
1 голос
/ 01 июля 2011

Просто чтобы предложить другую альтернативу, хотя и не совсем LINQ, вы можете написать функцию на основе доходности для агрегации:

public static IEnumerable<int> SumSoFar(this IEnumerable<int> values)
{
  int sumSoFar = 0;
  foreach (int value in values)
  {
    sumSoFar += value;
    yield return sumSoFar;
  }
}

Как и в BrokenGlass, это делает только один проход по данным, хотя в отличие от его возвращает итератор, а не список.

(досадно вы не можете легко сделать это универсальным для числового типа в списке .)

1 голос
/ 01 июля 2011

Чтобы использовать Linq и выполнять итерации по списку только после того, как вы можете использовать собственный агрегатор:

class Aggregator
{
    public List<int> List { get; set; }
    public int Sum { get; set; }
}

..

var seq = new List<int> { 1, 3, 12, 19, 33 };
var aggregator = new Aggregator{ List = new List<int>(), Sum = 0 };
var aggregatorResult = seq.Aggregate(aggregator, (a, number) => { a.Sum += number; a.List.Add(a.Sum); return a; });
var result = aggregatorResult.List;
1 голос
/ 01 июля 2011
var seq = new List<int> { 1, 3, 12, 19, 33 }; 

for (int i = 1; i < seq.Count; i++)
{
   seq[i] += seq[i-1];
}
...