4

Assuming I have the following

var list = new []{
            new { Price = 1000, IsFirst = true}, 
            new { Price = 1100, IsFirst = false},
            new { Price = 450, IsFirst = true},
            new { Price = 300, IsFirst = false}
};

and I want to generate the following output:

Price  IsFirst  First Second Final
----------------------------------
1000   True     1000  0      1000
1100   False    0     1100   -100
450    True     450   0      350
300    False    0     300    50

Is it possible to have some sort of aggregate function processed up to current row? I like to have all the stuff in pure LINQ but as of now I have no other choice than manually iterating the list and sum the column conditionally.

var result = list.Select(x => new 
{
    Price = x.Price,
    IsFirst = x.IsFirst,
    First = x.IsFirst ? x.Price : 0,
    Second = !x.IsFirst ? x.Price : 0,
    Final = 0 // ???
}).ToList();

int sum = 0;

for(int i=0; i<result.Count(); i++)
{
    sum += (result[i].IsFirst ? result[i].Price : - result[i].Price);   
    // updating Final value manually
}
Mohsen Afshin
  • 13,273
  • 10
  • 65
  • 90
  • If you're playing with `IEnumerable` and not `IQueryable` this should work http://stackoverflow.com/questions/1834753/linq-to-sql-and-a-running-total-on-ordered-results – ta.speot.is Jul 13 '14 at 08:59

3 Answers3

3

The easiest way to do this is to use the Microsoft Reactive Extension Team's "Interactive Extension" method Scan. (Use NuGet and look for Ix-Main.)

var query =
    list
        .Scan(new
        {
            Price = 0,
            IsFirst = true,
            First = 0,
            Second = 0,
            Final = 0
        }, (a, x) => new
        {
            Price = x.Price,
            IsFirst = x.IsFirst,
            First = x.IsFirst ? x.Price : 0,
            Second = !x.IsFirst ? x.Price : 0,
            Final = a.Final + (x.IsFirst ? x.Price : - x.Price)
        });

This gives:

result

However, you can do it with the built-in Aggregate operator like this:

var query =
    list
        .Aggregate(new []
        {
            new
                {
                    Price = 0,
                    IsFirst = true,
                    First = 0,
                    Second = 0,
                    Final = 0
                }
        }.ToList(), (a, x) =>
        {
            a.Add(new
            {
                Price = x.Price,
                IsFirst = x.IsFirst,
                First = x.IsFirst ? x.Price : 0,
                Second = !x.IsFirst ? x.Price : 0,
                Final = a.Last().Final + (x.IsFirst ? x.Price : - x.Price)
            });
            return a;
        })
        .Skip(1);

You get the same result.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
2

What you want is called a running total.

As far as I know, there is no built-in method to do this in LINQ, but various people have written extension methods to do that. One that I quickly found online is this one:

Based on this, it should be fairly easy to turn your code into an extension method that

  • resets the intermediate value when encountering an item with IsFirst = true,
  • otherwise decrements the value,
  • and yields it.
Heinzi
  • 167,459
  • 57
  • 363
  • 519
0

You can do something like this:

int final = 0;
var result = list.Select(x => new
{
    Price = x.Price,
    IsFirst = x.IsFirst,
    First = x.IsFirst ? x.Price : 0,
    Second = !x.IsFirst ? x.Price : 0,
    Final = x.IsFirst ? final+=x.Price : final-=x.Price
}).ToList();

But you would need to define an integer (final) outside of the linq expression to keep track of the total sum.

Yuan Shing Kong
  • 674
  • 5
  • 15