4

How to get the result set from the below example.

public class Parent
{
    public string Id { get; set; }
    public List<Child> Children { get; set; }
}

public class Child : Parent
{
    public bool Isleaf { get; set; }
}
Child c1 = new Child();
c1.Id = "c1";
c1.Isleaf = false;

Child c2 = new Child();
c2.Id = "c2";
c2.Isleaf = true;

Child c11 = new Child();
c11.Id = "c11";
c11.Isleaf = true;

Child c12 = new Child();
c12.Id = "c12";
c12.Isleaf = false;


Child c121 = new Child();
c121.Id = "c121";
c121.Isleaf = true;

c12.Children = new List<Child>() { c121 };
c1.Children = new List<Child>() { c11, c12 };

Parent p = new Parent();
p.Id = "P1";
p.Children = new List<Child>() { c1, c2 };

From above collection i want to fetch the list of all children that has leaf node true i.e. List leafNode=new List {c2,c11,c21};

Cœur
  • 37,241
  • 25
  • 195
  • 267
kk_gaurav
  • 63
  • 1
  • 8

6 Answers6

4

I wouldn't recommend trying to solve this with a lambda expression. A recursive method would probably suit:

void FindLeaves(Parent p, ICollection<Child> leaves)
{
    if (p.Children != null)
        foreach (var child in p.Children)
        {
            if (child.Isleaf)
                leaves.Add(child);
            FindLeaves(child, leaves);
        }
}

var leaves = new List<Child>();
FindLeaves(p, leaves);

If the leaf same node can appear in multiple places in the tree, you probably want to add some logic to prevent including any child twice. e.g.

if (child.IsLeaf && !leaves.Contains(child) 
    leaves.Add(child)

A recursive solution may not suit if any of the following uncommon conditions are true:

  • There is a chance for cycles in your tree (e.g. ChildA -> ChildB -> ChildA). (Stack overflow unless messy logic to avoid cycles is added)
  • The tree may be extremely deep. (Stack overflow)
  • The tree is very large and performance is absolutely paramount.
Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
  • Better... public static ICollection FindLeaves(this Parent p) { var leaves = new List(); if (p.Children != null) foreach (var child in p.Children) { if (child.Isleaf) leaves.Add(child); else leaves.AddRange(FindLeaves(child)); } } – Mick Oct 01 '13 at 05:14
  • 1
    Be careful! Depending on the structure in the real world recursive algorithms can cause stack overflows. If you're talking about a massive structure I'd write it without recursion – Mick Oct 01 '13 at 05:21
  • @Mick: a stack overflow would be possible if there are cycles in the tree, yes. Answer edited to reflect this – Igby Largeman Oct 01 '13 at 05:36
  • Doesn't require infinite loops just requires the tree depth to be great enough that you run out of stack executing your code. The point is, it's not infinite, it depends on the size of what you're passing but you'll get probably 15,000 - 30,000 recursions before kapow. Plus non-recursive algorithms perform better, even if it doesn't crash, implementing anything as a recursive algorithm adds weight. I only use recursion when I know absolutely that the iterations will be small i.e 100 tops. – Mick Oct 01 '13 at 05:52
  • How deep can you go?... http://stackoverflow.com/questions/4513438/c-sharp-recursion-depth-how-deep-can-you-go – Mick Oct 01 '13 at 05:59
  • @Mick: True, though such deep trees aren't common in most domains. I also expect programmers to exercise common sense when choosing whether or not to use any particular algorithm. However, I've added another note to the answer about this risk. – Igby Largeman Oct 01 '13 at 06:19
1

This solution is based on Igby Largeman, but it uses a stack and removes recursion to prevent stack overflow:

void FindLeaves(Parent p, ICollection<Child> leaves)
{
    if (p.Children != null) return;

    var toVisit = new Stack<Child>(p.Children());

    while (toVisit.Count > 0) {
        var current = toVisit.Pop(); 

        foreach (var child in current.Children)
        {    
            if (child.Isleaf)
                leaves.Add(child);
            else
                toVisit.Push(child);
        }
    }
}
Teudimundo
  • 2,610
  • 20
  • 28
0

Well for starters I'd implement IsLeaf as

    public bool Isleaf
    {
        get
        {
            return Children.Any();
        }
    }

Secondly I'd have a second collection with all nodes to make flat queries across all the nodes in your tree easy. You could make another class called RootNode... have a property AllNodes. Then you can do...

var leafNodes = rootNode.AllNodes.Where(a => a.Isleaf);

The Telerik ASP.NET RadTreeNode control does just this to make life easier.

http://www.telerik.com/community/forums/aspnet-ajax/treeview/radtreeview-looping.aspx

Mick
  • 6,527
  • 4
  • 52
  • 67
0

try this :

Get the children for each parent that has Isleaf = true;

var child1 = Parent.Children.FirstOrDefault(a => a.Isleaf);  // result is c2
var child2 = c12.Children.FirstOrDefault(a => a.Isleaf);     // result is c121
var child3 = c1.Children.FirstOrDefault(a => a.Isleaf);      // result is c11

List leafNode=new List {child1 ,child2 ,child3 };

Not this will work only if you have this Parent -> Children Structure. If you add more children, you need to have foreach loop. The reason I did it this way is I do not know what is the connection you are trying to make when adding children to a parents. Otherwise if you have all your children inside the parent children list attribute. You can simply have a foreach loop.

user123456
  • 2,524
  • 7
  • 30
  • 57
0
public static class SearcTree
{
    public static IEnumerable<T> GetLeaf<T>(this T rootNode, Func<T, IEnumerable<T>> childrenFunc)
    {
        var childrens = childrenFunc(rootNode);
        var haschild = childrens != null && childrens.Any();
        if (!haschild)
            yield return rootNode;
        else
            foreach (var node in childrenFunc(rootNode))
            {
                foreach (var child in GetLeaf(node, childrenFunc))
                {
                    childrens = childrenFunc(child);
                    haschild = childrenFunc(child) != null && childrens.Any();
                    if (!haschild)
                        yield return child;
                }
            }
    }
}



  //Uses: 
     var allLeaf = p.GetLeaf(root => root.Children);
kk_gaurav
  • 63
  • 1
  • 8
0

I've written a generic solution that is based on Teudimundo's solution. This is for object trees where the object contains a collection of itself. Here is a simple class to demonstrate.

public class Thing
{
    public IEnumerable<Thing> Children { get; set; }
    public bool IsLeaf => Children == null || !Children.Any();
}

So, with this very basic setup, you can see that it creates a tree of Thing. I have Children and a property, IsLeaf, letting us know if this Thing is a leaf. From this, I created an extension method for IEnumerable that allows you to find all the leaves for this kind of scenario.

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Leaves<TSource>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> children, Func<TSource, bool> isLeaf)
    {
        var nodes = new Stack<TSource>(source);
        while (nodes.Any())
        {
            var current = nodes.Pop();
            if(isLeaf(current))
            {
                yield return current;
                continue;
            }
            foreach (var child in children(current))
            {
                if (isLeaf(child))
                {
                    yield return child;
                }
                else
                {
                    nodes.Push(child);
                }
            }
        }
    }
}

This is great because it works in Linq statements. So, if you have a collection of Thing, you can easily traverse the tree to find all the leaves of your collection. Here is what calling this extension method looks like.

var leaves = things.Leaves(t => t.Children, t => t.IsLeaf);

things is an IEnumerable of Thing. It is easiest to make a new List<Thing>() with a bunch of Thing in it, and assign it to things. The parameters to Leaves are the two properties we need to traverse the tree. The first is Children which are the child nodes of the tree. The second is IsLeaf which is the property that tells us if this Thing is a leaf or not. This call returns an IEnumerable<Thing> containing only leaves from things. Use .ToList() to make it to a list if need be. I don't think it can get much simpler than that!

Community
  • 1
  • 1
Michael Yanni
  • 1,476
  • 4
  • 17
  • 29