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

本文关键字:集合 获取 叶节点 嵌套 何使用 lambda 表达式 | 更新日期: 2023-09-27 18:33:20

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

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

从上面的集合中,我想获取具有叶节点 true 的所有子项的列表,即列表叶节点=新列表 {c2,c11,c21};

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

我不建议尝试使用 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 -> ChildB ->孩子A(。(堆栈溢出,除非添加混乱的逻辑以避免循环(
  • 这棵树可能非常深。(堆栈溢出(
  • 这棵树非常大,性能绝对是最重要的。

此解决方案基于 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);
        }
    }
}

试试这个:

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

不是这只有在您有这个父>子结构时才有效。如果添加更多子项,则需要具有 foreach 循环。我这样做的原因是我不知道您在将孩子添加到父母时试图建立什么联系。否则,如果所有子项都在父项列表属性中。您可以简单地拥有一个 foreach 循环。

对于初学者来说,我会将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

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

我写了一个基于 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);

thingsThingIEnumerable。最简单的方法是制作一个包含一堆Thingnew List<Thing>(),并将其分配给things.要Leaves的参数是遍历树所需的两个属性。第一个是树的子节点Children。第二个是IsLeaf,它是告诉我们这个Thing是否是叶子的属性。此调用返回仅包含来自things的叶子的IEnumerable<Thing>。如果需要,请使用.ToList()将其添加到列表中。我认为没有比这更简单的了!