18

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?

Kirill Polishchuk
  • 54,804
  • 11
  • 122
  • 125
Ritch Melton
  • 11,498
  • 4
  • 41
  • 54

8 Answers8

24

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);
Stephen Swensen
  • 22,107
  • 9
  • 81
  • 136
  • It's funny how I looked through the whole list of `Enumerable` methods before realizing it's part of F# but not LINQ. – Dan Abramov Jul 01 '11 at 18:20
  • 1
    Reactive Extensions include extra Linq operators, including `Scan`, though I can't find a handy concise link to point to. – Benjol Aug 24 '11 at 11:55
  • 5
    Note: the added Enumerable operators that can be found in the Reactive/Interactive Extensions don't include the seed value as the first value of the Scan sequence, which is different from the above. – Niall Connaughton Mar 02 '15 at 06:25
8

"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; });
Kirill Polishchuk
  • 54,804
  • 11
  • 122
  • 125
3

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);
Nathan Phillips
  • 11,899
  • 1
  • 31
  • 24
2
var seq = new List<int> { 1, 3, 12, 19, 33 };

var summed = new List<int>();

seq.ForEach(i => summed.Add(i + summed.LastOrDefault()));
Rick Liddle
  • 2,684
  • 19
  • 31
2

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

Community
  • 1
  • 1
Rup
  • 33,765
  • 9
  • 83
  • 112
1
var seq = new List<int> { 1, 3, 12, 19, 33 }; 

for (int i = 1; i < seq.Count; i++)
{
   seq[i] += seq[i-1];
}
HitLikeAHammer
  • 2,679
  • 3
  • 37
  • 53
1

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;
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
  • 1
    Overkill. OP's solution itself is more elegant. OP's looking for something *better* than his/her own solution. – Adriano Carneiro Jul 01 '11 at 17:47
  • @Adrian but this is better in the sense it's O(N) rather than O(N^2) like OP's - it iterates over the list only once. (Assuming List.Add is O(N), that is.) – Rup Jul 01 '11 at 17:52
  • 1
    @Adrian: Overkill depends on the eye of the beholder: This is O(n), OPs solution, while definitely simpler is O(n^2). So if you have a very long sequence it will be much more usable - on the flip side if you have only short sequences I'd go for readability and a simple approach myself. To combine both my personal choice actually would be a simple for loop: readable and O(n) – BrokenGlass Jul 01 '11 at 17:53
  • @BrokenGlass: Every **opinion** based concept depends on the eye of the beholder. It's an opinion :) OP's IEnumerable is 5 items count only. But for general purposes, if I were to write my own, I would take @HitLikeAHammer's approach (which is O(n)) and write an extension method – Adriano Carneiro Jul 01 '11 at 17:57
-1

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.

AFract
  • 8,868
  • 6
  • 48
  • 70