Summing the previous values in an IEnumerable

5.7k Views Asked by At

I have a sequence of numbers:

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

and I want to transform that into a new sequence where the number is added to the preceding numbers to create a new sequence:

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

I came up with the following, but I dislike the state variable 'count'. I also dislike the fact that I'm using the values Enumerable without acting on it.

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

How else could it be done?

8

There are 8 best solutions below

3
Stephen Swensen On BEST ANSWER

This is a common pattern in functional programming which in F# is called scan. It's like C#'s Enumerable.Aggregate and F#'s fold except that it yields the intermediate results of the accumulator along with the final result. We can implement scan in C# nicely with an extension method:

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

And then use it as follows:

var seq = new List<int> { 1, 3, 12, 19, 33 };
var transformed = seq.Scan(((state, item) => state + item), 0).Skip(1);
3
HitLikeAHammer On
var seq = new List<int> { 1, 3, 12, 19, 33 }; 

for (int i = 1; i < seq.Count; i++)
{
   seq[i] += seq[i-1];
}
0
Rick Liddle On
var seq = new List<int> { 1, 3, 12, 19, 33 };

var summed = new List<int>();

seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
6
Kirill Polishchuk On

"Pure" LINQ:

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

One more "pure" LINQ O(n):

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

One more LINQ, with state maintenance:

var tmp = 0;
var result = les.Select(a => { tmp += a; return tmp; });
4
BrokenGlass On

To use Linq and only iterate over the list once you could use a custom aggregator:

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;
0
Rup On

Just to offer another alternative, albeit not really LINQ, you could write a yield-based function to do the aggregation:

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

Like BrokenGlass's this makes only a single pass over the data although unlike his returns an iterator not a list.

(Annoyingly you can't easily make this generic on the numeric type in the list.)

0
Nathan Phillips On

Stephen Swensen's answer is great, scan is exactly what you need. There is another version of scan though that doesn't require a seed, which would be slightly more appropriate for your exact problem.

This version requires that your output element type is the same as your input element type, which it is in your case, and gives the advantage of not needing you to pass in a 0 and then Skip the first (0) result.

You can implement this version of scan in C# as follows:

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

And then use it as follows:

IEnumerable<int> seq = new List<int> { 1, 3, 12, 19, 33 };
IEnumerable<int> transformed = seq.Scan((state, item) => state + item);
1
AFract On

All answers already posted are working fine. I just would to add two things :

  • MoreLinq already have a Scan method in it, with a lot of very useful functions that are not native in LINQ. It's a great library to know for this kind of applications so I would like to recall it.

  • Before to see the answers above, I wrote my own Scan :

    public static IEnumerable<T> Scan<T>(IEnumerable<T> input, Func<T, T, T> accumulator)
    {
      if (!input.Any())
          yield break;
    
      T state = input.First();
      yield return state;
    
      foreach (var value in input.Skip(1))
      {
          state = accumulator(state, value);
          yield return state;
      }
    

    }

Works well but very probably less efficient than using @Nathan Phillips version due to multiple accesses to the collection. Enumerator probably does it better.