HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Sum(), ForEach() with Lambda in a hierarchical List Collection

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withcollectionsumforeachlisthierarchicallambda

Problem

I have to list the number of documents related to items in a hierarchical tree.

The specification states:
The tree will only ever be 2 levels deep.
The higher level items will list not only the number of documents in it's folder, but also the total count of documents in the child folders. They want this total to include the documents in the current folder.

I've developed the solution below, it works, however it just seems complicated, hard to maintain and probably not the most efficient way to do this. Also, if the tree is ever more than 2 levels deep, this will fail.

```
class Item
{
public int Id { get; set; }
public int ParentId { get; set; }
public int DocumentCount { get; set; }
public int ChildItemCount { get; set; }
public int ChildCount { get; set; }
}

static class Program
{
static void Main()
{
var items = new List
{
new Item {Id = 1, ParentId = 0},
new Item {Id = 2, ParentId = 1},
new Item {Id = 3, ParentId = 1},
new Item {Id = 4, ParentId = 2},
new Item {Id = 5, ParentId = 2},
new Item {Id = 6, ParentId = 3},
new Item {Id = 7, ParentId = 3},
new Item {Id = 8, ParentId = 3}
};
// setup
var rnd = new Random();
items.ForEach(c => c.DocumentCount = rnd.Next(1, 10));

// act
items.ForEach(z => z.ChildCount = items.Count(x => x.ParentId == z.Id));
items.ForEach(z => z.ChildItemCount = items.Where(x => x.ParentId == z.Id).Sum(y => y.DocumentCount) + z.DocumentCount);
items.Where(k => k.ParentId == 0).ToList().ForEach(z => z.ChildItemCount = items.Where(x => x.ParentId == z.Id).Sum(y => y.ChildItemCount) + z.DocumentCount);

// output
items.ForEach(i => Console.WriteLine("Id: {0}, ParentId: {1}, DocumentCount: {2}, ChildCount: {3}, ChildItemCount: {4}", i.Id, i.ParentId, i.DocumentCount,i.ChildCount, i.ChildItemCount));
Console.Re

Solution

You can create lookup for children. That will involve single enumeration over items collection instead of enumerating all items for each calculation of each item.

I.e. you currently have 2 N N iterations for calculating child count and child item count. With lookup you will have N + N + N iterations

var children = items.ToLookup(i => i.ParentId);

foreach(var item in items) 
{
    var subitems = children[item.Id];
    item.ChildCount = subitems.Count();
    item.ChildItemCount = subitems.Sum(i => i.DocumentCount) + item.DocumentCount;
}


Explanation: Let's look on this line

items.ForEach(z => z.ChildItemCount = 
    items.Where(x => x.ParentId == z.Id).Sum(y => y.DocumentCount) + z.DocumentCount);


Here for each item in items collection you enumerate every item in items collection to get only items which are children of current. That gives you N * N iterations totally. Count is calculated same way.

Now lets look on my code.

var children = items.ToLookup(i => i.ParentId);


Here lookup is created. Internally it looks like enumerating over items and adding them to appropriate groupings of lookup. You can think of lookup as a Dictionary>.

Next we do enumerating of all items (that's another N). In body of this loop we get children of item (getting appropriate group by key is pretty fast O(1) operation, comparing to enumerating all items and filtering them). Good news here is behavior of lookup when key not found - it simply returns EmptyEnumerable (i.e. empty array of TValue), so we don't need to check anything - further calculations will work without problems:

foreach(var item in items)
{
    var subitems = children[item.Id];
    // ...
}


Next we do two operations. First is getting count of items in group. Grouping implements ICollection with Count property, and it simply returns count of items in group, without enumerating them:

item.ChildCount = subitems.Count();


Second operation is calculating sum of documents in group. Thus total number of items in all groups will be N then enumerating all groups will take N iterations:

item.ChildItemCount = subitems.Sum(i => i.DocumentCount) + item.DocumentCount;


UPDATE: Thus you want ChildItemCount to contain sum

Code Snippets

var children = items.ToLookup(i => i.ParentId);

foreach(var item in items) 
{
    var subitems = children[item.Id];
    item.ChildCount = subitems.Count();
    item.ChildItemCount = subitems.Sum(i => i.DocumentCount) + item.DocumentCount;
}
items.ForEach(z => z.ChildItemCount = 
    items.Where(x => x.ParentId == z.Id).Sum(y => y.DocumentCount) + z.DocumentCount);
var children = items.ToLookup(i => i.ParentId);
foreach(var item in items)
{
    var subitems = children[item.Id];
    // ...
}
item.ChildCount = subitems.Count();

Context

StackExchange Code Review Q#51267, answer score: 4

Revisions (0)

No revisions yet.