11

I have a loop like the following, can I do the same using multiple SUM?

foreach (var detail in ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload &&
                                                                pd.InventoryType == InventoryTypes.Finished))
{
     weight += detail.GrossWeight;
     length += detail.Length;
     items  += detail.NrDistaff;
}
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Alessandro
  • 153
  • 1
  • 1
  • 8
  • 1
    LINQ is not the be-all and end-all of data manipulation, and there's still nothing wrong with a for loop. – SLaks Nov 09 '09 at 23:09
  • Funny though @SLaks that this is one of the rare cases where LINQ doesn't offer a reasonable solution. – PeterX Mar 06 '15 at 05:05

5 Answers5

8

Technically speaking, what you have is probably the most efficient way to do what you are asking. However, you could create an extension method on IEnumerable<T> called Each that might make it simpler:

public static class EnumerableExtensions
{
    public static void Each<T>(this IEnumerable<T> col, Action<T> itemWorker)
    {
        foreach (var item in col)
        {
            itemWorker(item);
        }
    }
}

And call it like so:

// Declare variables in parent scope
double weight;
double length;
int items;

ArticleLedgerEntries
    .Where(
        pd => 
           pd.LedgerEntryType == LedgerEntryTypeTypes.Unload &&
           pd.InventoryType == InventoryTypes.Finished
    )
    .Each(
        pd => 
        {
            // Close around variables defined in parent scope
            weight += pd.GrossWeight; 
            lenght += pd.Length;
            items += pd.NrDistaff;
        }
    );

UPDATE: Just one additional note. The above example relies on a closure. The variables weight, length, and items should be declared in a parent scope, allowing them to persist beyond each call to the itemWorker action. I've updated the example to reflect this for clarity sake.

jrista
  • 32,447
  • 15
  • 90
  • 130
  • 2
    +1, nice answer. Note, that *MoreLinq* already have a `ForEach` extension method, declared for `IEnumerable`. Another note, Eric Lippert [doesn't like this idea](http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx) – Ilya Ivanov Mar 02 '14 at 10:09
5

You can call Sum three times, but it will be slower because it will make three loops.

For example:

var list = ArticleLedgerEntries.Where(pd => pd.LedgerEntryType == LedgerEntryTypeTypes.Unload
                                   && pd.InventoryType == InventoryTypes.Finished))

var totalWeight = list.Sum(pd => pd.GrossWeight);
var totalLength = list.Sum(pd => pd.Length);
var items = list.Sum(pd => pd.NrDistaff); 

Because of delayed execution, it will also re-evaluate the Where call every time, although that's not such an issue in your case. This could be avoided by calling ToArray, but that will cause an array allocation. (And it would still run three loops)

However, unless you have a very large number of entries or are running this code in a tight loop, you don't need to worry about performance.


EDIT: If you really want to use LINQ, you could misuse Aggregate, like this:

int totalWeight, totalLength, items;

list.Aggregate((a, b) => { 
    weight += detail.GrossWeight;
    length += detail.Length;
    items  += detail.NrDistaff;
    return a;
});

This is phenomenally ugly code, but should perform almost as well as a straight loop.

You could also sum in the accumulator, (see example below), but this would allocate a temporary object for every item in your list, which is a dumb idea. (Anonymous types are immutable)

var totals = list.Aggregate(
    new { Weight = 0, Length = 0, Items = 0},
    (t, pd) => new { 
        Weight = t.Weight + pd.GrossWeight,
        Length = t.Length + pd.Length,
        Items = t.Items + pd.NrDistaff
    }
);
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Ok. I realize that there isn't an easy way to do this using LINQ. I'll take may foreach loop because I understood that it isn't so bad. Thanks to all of you. – Alessandro Nov 09 '09 at 22:51
  • Can you please comment on the answer by user805138? What does the performance look like in his approach? – Andrzej Gis Aug 23 '12 at 01:07
  • @gisek: The `group x by 1` is completely useless and very stupid; it introduces LINQ syntax for no reason at all. Other than that, it's identical to my first code; it uses two extra loops. – SLaks Aug 23 '12 at 02:40
  • Is this supported by linq to entities ? – Jayantha Lal Sirisena Mar 07 '13 at 12:27
  • @Jayantha: I assume you're asking about the `Aggregate` mis-usages. The first one certainly isn't (block lambdas cannot become expression trees); the second one almost definitely isn't. – SLaks Mar 07 '13 at 14:46
2

You could also group by true - 1 (which is actually including any of the items and then have them counted or summered):

 var results = from x in ArticleLedgerEntries
                       group x by 1
                       into aggregatedTable
                       select new
                                  {
                                      SumOfWeight = aggregatedTable.Sum(y => y.weight),
                                      SumOfLength = aggregatedTable.Sum(y => y.Length),
                                      SumOfNrDistaff = aggregatedTable.Sum(y => y.NrDistaff)
                                  };

As far as Running time, it is almost as good as the loop (with a constant addition).

Tomer
  • 4,382
  • 5
  • 38
  • 48
  • The `group by` is completely useless and rather confusing. Just do `var results = new { ... = ArticleLedgerEntries.Sum(...), ... }` – SLaks Aug 23 '12 at 02:41
0

You'd be able to do this pivot-style, using the answer in this topic: Is it possible to Pivot data using LINQ?

Community
  • 1
  • 1
Jan Jongboom
  • 26,598
  • 9
  • 83
  • 120
0

Ok. I realize that there isn't an easy way to do this using LINQ. I'll take may foreach loop because I understood that it isn't so bad. Thanks to all of you

Alessandro
  • 153
  • 1
  • 1
  • 8
  • You should not post an answer on SO like you would post a reply on a topic in a forum. Only do this if you answer your own question. Usually you add *UPDATE* to your original question as a sort of reply to the answers and comments on your question. – Mike de Klerk Dec 28 '16 at 09:07