tags:

views:

1267

answers:

2

I have an object:

IObject
{
    string Account,
    decimal Amount
}

How do I group by Account and Sum the Amount, returning a List without Linq.

2.0 Framework ... that is why no Linq.

Here is what I have:

    ListofObjects = List<IObject>;

    foreach (var object in objects)
    {
        var objectToAdd = new Object(object);

        var oa = ListofObjects.Find(x => x.Account == objectToAdd.Account);

        if (oa == null)
        {
            ListofObjects.Add(objectToAdd);
        }
        else
        {
            ListofObjects.Remove(oa);
            oa.Amount = objectToAdd.Amount;
            ListofObjects.Add(oa);
        }


    }
+13  A: 

Easiest answer: use LINQBridge and get all your LINQ to Objects goodness against .NET 2.0... works best if you can use C# 3 (i.e. VS2008 but targeting .NET 2.0).

If you really can't do that, you'll basically need to keep a dictionary from a key to a list of values. Iterate through the sequence, and check whether it already contains a list - if not, add one. Then add to whatever list you've found (whether new or old).

If you need to return the groups in key order, you'll need to also keep a list of keys in the order in which you found them. Frankly it's a pain... just get LINQBridge instead :)

(Seriously, each individual bit of LINQ is actually fairly easy to write - but it's also quite easy to make off-by-one errors, or end up forgetting to optimize something like Count() in the case where it's actually an ICollection<T>... There's no need to reinvent the wheel here.)

EDIT: I was about to write some code, but then I noticed that you want a list returned... a list of what? A List<IList<IObject>>? Or are you actually trying to group and sum in one go? If so, don't you want a list of pairs of key and amount? Or are you going to reuse the same class that you've already got for a single account, but as the aggregate? If it's the latter, here's some sample code:

public static IList<IObject> SumAccounts(IEnumerable<IObject> data)
{
    List<IObject> ret = new List<IObject>();
    Dictionary<string, IObject> map = new Dictionary<string, IObject>();

    foreach (var item in data)        
    {
        IObject existing;
        if (!map.TryGetValue(item.Account, out existing))
        {
            existing = new IObject(item.Account, 0m);
            map[item.Account] = existing;
            ret.Add(existing);
        }
        existing.Amount += item.Amount;
    }
    return ret;
}

Admittedly the extra efficiency here due to using a Dictionary for lookups will be pointless unless you've got really quite a lot of accounts...

EDIT: If you've got a small number of accounts as per your comment, you could use:

public static IList<IObject> SumAccounts(IEnumerable<IObject> data)
{
    List<IObject> ret = new List<IObject>();

    foreach (var item in data)        
    {
        IObject existing = ret.Find(x => x.Account == item.Account);
        if (existing == null)
        {
            existing = new IObject(item.Account, 0m);
            ret.Add(existing);
        }
        existing.Amount += item.Amount;
    }
    return ret;
}
Jon Skeet
dang! I forgot about Joe's LINQBridge. And Jon is so close to 100K... +1 !;)
Mitch Wheat
Thanks, but I doubt I can get signoff to add it to my project to help 12 out 1000 lines of code.
Martin
I suspect you'd find it would end up helping rather more than that...
Jon Skeet
Is it worth it to change my code for a max of 4 accounts (though, this code will be ran a ton each day).
Martin
Well, if you've only got 4 accounts then your original code won't be too bad... but could still be improved a bit. (You're adding and removing for no good reason.) I'll edit to add an answer to that effect...
Jon Skeet
Thanks a bunch Jon, I really appreciate it.
Martin
+3  A: 

Use a dictionary to hold the results. Locating an item in a dictionary is close to an O(1) operation, so it's a lot faster than searching for items in a list.

Dictionary<string, decimal> sum = new Dictionary<string, decimal>();

foreach (IObject obj in objects) {
   if (sum.ContainsKey(obj.Account)) {
      sum[obj.Account].Amount += obj.Amount;
   } else {
      sum.Add(obj.Account, obj.Amount);
   }
}
Guffa
Finding an entry in a dictionary is faster than a linear list search when you have a lot of items... for a very small dictionary/list it may well be slower. Don't forget that O(...) is a measure of how the complexity changes as N increases, not an absolute operation count.
Jon Skeet
@Jon: Yes, if there are very few items the Dictionary may be slower, but then it's so little time anyway that it rarely matters. If the code runs so often that it's a problem, then it should probably be solved in a completely different way anyway.
Guffa