Linq-to-objects: creating a two-level hierarchy from a flat source

Say I have this simple structure

class FooDefinition { public FooDefinition Parent { get; set; } } class Foo { public FooDefinition Definition { get; set; } } class Bar { public ICollection<Foo> Foos { get; set; } } 

A Bar has a list of Foos , which can be simple (without parent / child relationships) or nested with only one level (i.e. a parent Foo has many child Foos ). As you can see here, relationships are specified in FooDefinition , and not in Foo itself .

I need to create a Foos list correctly grouped by this hierarchy. Consider the following source data:

 var simpleDefinition = new FooDefinition(); var parentDefinition = new FooDefinition(); var childDefinition = new FooDefinition { Parent = parentDefinition }; var bar = new Bar { Foos = new[] { new Foo { Definition = simpleDefinition }, new Foo { Definition = parentDefinition }, new Foo { Definition = childDefinition } }}; 

I would like to get a collection of top-level items with their children. The corresponding data structure is likely to be IEnumerable<IGrouping<Foo, Foo>> .

The result will look like this:

  • Item 1 (simple)
  • Item 2 (parent)
    • Item 3 (child)

And of course, I would like to do this with a purely functional Linq query. I have many of these, but my brain seems to be stuck today.

+4
source share
2 answers
 bar.Foos.Where(x => x.Definition.Parent == null) .Select(x => Tuple.Create(x, bar.Foos.Where(c => c.Definition .Parent == x.Definition ))); 

This will return IEnumerable<Tuple<Foo, IEnumerable<Foo>>> , where Item2 from Tuple contains children for the parent in Item1 . For your example, this returns two sets:

  • Item1 = simpleDefinition and Item2 containing an empty enumerated
  • Item1 = parentDefinition and Item2 containing an enumerated containing childDefinition

There may be a more elegant or faster way, but I could not come up with it ...

Well, I was a bit contrary to my own comment, but this is possible with GroupBy - at least almost:

 bar.Foos.Where(x => x.Definition.Parent == null) .GroupBy(x => x, x => bar.Foos.Where(c => c.Definition.Parent == x.Definition)); 

This will return IEnumerable<IGrouping<Foo, IEnumerable<Foo>>> .

Update:
I wanted to know if the solution you wanted was possible at all. Yes it:

 bar.Foos.Where(x => x.Definition.Parent != null) .GroupBy(x => bar.Foos.Where(y => y.Definition == x.Definition.Parent) .Single(), x => x) .Union(bar.Foos.Where(x => x.Definition.Parent == null && !bar.Foos.Any(c => c.Definition.Parent == x.Definition)) .GroupBy(x => x, x => (Foo)null)); 

But I really do not want to know big O of this, and it really should not be used; -)

+3
source

If you add a class and method, you can go to IEnumerable<IGrouping<Foo,Foo>> .

  class FooRelation{ public Foo Parent {get; set;} public Foo Child {get; set;} } static IEnumerable<FooRelation> GetChildren(Bar source, Foo parent){ var children = source.Foos.Where(c => c.Definition.Parent == parent.Definition); if(children.Any()) return children.Select(c => new FooRelation{Parent = parent, Child = c}); return new FooRelation[]{new FooRelation{Parent = parent}}; } 

You might even be able to collapse this static method into this query ... but it will be randomly:

  var r = bar.Foos.Where(x => x.Definition.Parent == null) .SelectMany(x => GetChildren(bar, x)) .GroupBy(fr => fr.Parent, fr => fr.Child); 
0
source

All Articles