如何使用lambda表达式从嵌套集合中获取叶节点

时间:2013-10-01 04:31:11

标签: c# .net linq

如何从以下示例中获取结果集。

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 };

从上面的集合中我想获取叶节点为真的所有子节点的列表,即List leafNode = new List {c2,c11,c21};

6 个答案:

答案 0 :(得分:4)

我不建议尝试用lambda表达式来解决这个问题。递归方法可能适合:

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 (child.IsLeaf && !leaves.Contains(child) 
    leaves.Add(child)

如果满足以下任何不常见条件,则递归解决方案可能不适合:

  • 树中有可能出现循环(例如ChildA - &gt; ChildB - &gt; ChildA)。 (堆栈溢出,除非添加杂乱的逻辑以避免循环)
  • 树可能非常深。 (堆栈溢出)
  • 树很大,性能绝对是最重要的。

答案 1 :(得分:1)

此解决方案基于Igby Largeman,但它使用堆栈并删除递归以防止堆栈溢出:

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);
        }
    }
}

答案 2 :(得分:0)

对于初学者来说,我实施的是IsLeaf

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

其次,我将拥有一个包含所有节点的第二个集合,以便轻松地在树中的所有节点上进行平面查询。您可以创建另一个名为RootNode的类...具有属性AllNodes。那你可以做......

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

Telerik ASP.NET RadTreeNode控件就是为了让生活更轻松。

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

答案 3 :(得分:0)

试试这个:

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 };

仅当您拥有此父级时,这不会起作用 - &gt;儿童结构。如果添加更多子项,则需要使用foreach循环。我这样做的原因是我不知道在向父母添加孩子时你想要做的连接是什么。否则,如果您的所有子项都在父子列表属性中。你可以简单地使用foreach循环。

答案 4 :(得分: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);

答案 5 :(得分:0)

我写了一个基于Teudimundo解决方案的通用解决方案。这适用于对象树,其中对象包含自身的集合。这是一个简单的演示类。

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

因此,通过这个非常基本的设置,您可以看到它创建了Thing的树。我有一个Children和一个属性IsLeaf,让我们知道这个Thing是否是一片叶子。由此,我为IEnumerable创建了一个扩展方法,允许您查找此类场景的所有叶子。

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);
                }
            }
        }
    }
}

这很棒,因为它适用于Linq语句。因此,如果您拥有Thing的集合,则可以轻松遍历树以查找集合的所有叶子。以下是调用此扩展方法的内容。

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

thingsIEnumerable Thing。最简单的做法是在new List<Thing>()中添加一堆Thing,并将其分配给thingsLeaves的参数是遍历树所需的两个属性。第一个是Children,它们是树的子节点。第二个是IsLeaf,它告诉我们这个Thing是否是一片叶子。此调用返回IEnumerable<Thing>仅包含来自things的树叶。如果需要,使用.ToList()将其设置为列表。我认为它不会比那简单得多!