【问题标题】:How to find total number of childs and Number of grand child levels under a hierarchical list parent in C#如何在 C# 中的分层列表父级下查找子级总数和大子级数
【发布时间】:2020-05-15 11:08:15
【问题描述】:

我的项目中有一个树形菜单。

数据如下所示

{ParentId = null, Id = 10, Name "a"}, 
{ParentId = null, Id = 33, Name "aa"}, 
{ParentId = 10 , Id = 11, Name "aaa"}, 
{ParentId = 10, Id = 12, Name "aaaa"}, 
{ParentId = 11, Id = 13, Name "aaaaa"}, 
{ParentId = 56 ,Id = 14, Name "aas"}, 
{ParentId = 78 , Id = 15, Name "adss"}, 
{ParentId = 99 , Id = 16, Name "ader"}

我已经创建了一个分层列表来保存数据

public class NavBarItem
    {
        public int? Id { get; set; }
        public int? ParentId { get; set; }
        public string Name{get;set;}
        public IEnumerable<NavBarItem> Children { get; set; }
        public int ChildCount { get; set; }
        public int HierarchyLevel { get; set; }
    }

而我的递归方法将从表中获取数据并将其绑定到层次列表

我想在这里得到的是每个父母的子女/孙子女总数。

例如 Parent A 有 child B 并且 Child B 有 Child C & D,那么 A 的总 ChildCount 应该是 3 , B 应该是 2 并且 C 应该是 0

我还想获得每个父母的层次结构级别。

在上面的示例中:父母 A 有孩子 B,而 B 有另一个孩子。所以对于父 A,hierarchyLevel 是 2,对于 B,它应该是 1,对于 C,它应该是 0

例如,如果我使用 Id = 10 的项目,它有层次结构二(孙级别的数量)

 {ParentId = 10 , Id = 11, Name "aaa"}, 
  {ParentId = 11, Id = 13, Name "aaaaa"}, 

有没有更好的方法,或者简单的方法我可以获得这个 ChildCount 以及 Hierarchy 级别。

儿童总数示例:

Input is Id = 10 

total childs = 3. 

目前的做法:

RecursiveMethod(List)
{
 for each through the list and find the count 
call the RecursiveMethod again 
}

【问题讨论】:

  • 到目前为止您尝试了哪些方法,您的代码遇到了哪些问题?
  • @PavelAnikhouski 我创建了另一种递归方法来完成它。寻找更好的选择。而不是递归方法。可能是一个很好的语法(即使它在高层执行相同的递归)
  • 为什么不在NavBarItem 上创建一个递归返回该节点的子节点数的方法?
  • @Knoop 你在 NavbarItem 中创建方法是什么意思?您的意思是由该类的构造函数调用的方法?
  • 嗯,你实际上想要完成什么有点不清楚,所以很难给出一个好的答案。但你也许可以这样做:dotnetfiddle.net/G9rbFv。无论您是想从构造函数还是其他我不知道的地方调用它,您都没有足够的共享来了解正在发生的事情以及您想要什么。

标签: c# list linq


【解决方案1】:

我对通用解决方案的尝试:

编辑:为解决方案添加了一些 cmets 和其他改进

    /// <summary>
    /// Maps the nodes in a tree
    /// </summary>
    /// <param name="node">The node to process</param>
    /// <param name="level">
    /// the level of the node in the tree,
    /// 0 for the root node,
    /// 1 for children to the root etc.</param>
    /// <param name="childResults"> The result values for each of the children to the node </param>
    /// <returns> the result value for this node</returns>
    public delegate TResult TreeMapper<in T, TResult>(T node, int level, IEnumerable<TResult> childResults);

    /// <summary>
    /// Maps each node in a tree
    /// </summary>
    /// <param name="root">The root object of the tree</param>
    /// <param name="getChildren">Method to return all children of a node in the tree</param>
    /// <param name="map">
    /// Maps an item to some other type
    /// Inputs are:
    /// 1: The node of the tree
    /// 2: The level of the tree, starting with 0 for the root node
    /// 3: The results from each child to the node
    /// Returns: the result for the node
    /// </param>
    public static TResult MapChildren<T, TResult>(
        T root, 
        Func<T, IEnumerable<T>> getChildren,
        TreeMapper<T, TResult> map)
    {
        return RecurseBody(root, 0);

        TResult RecurseBody(T item, int level) 
            => map(item, level, getChildren(item).Select(child => RecurseBody(child, level + 1)));
    }

这可以在描述树的任何类型的对象上递归,并计算某种值。如果使用不同的映射方法,这可用于计算树的各种属性: 计算树中的节点总数:

(t, l, children) => children.Sum(c => c)+1;

获取树的最大层数:

(t, l, children) => children.DefaultIfEmpty(l).Max()

该方法只为整个树产生一个结果。如果要保留每个节点的结果,可以更新节点本身,或者保留带有节点->结果映射的字典

计算树中每个项目的级别和子项数量的单元测试,类似于您的示例:

 public class TestItem
    {
        public TestItem(string name, TestItem[] children )
        {
            Children = children;
            Name = name;
        }
        public TestItem(string name) : this(name, new TestItem[0])
        { }
        public string Name { get; }
        public TestItem[] Children { get; }
    }

    [Test]
    public void Test()
    {
        TestItem test = new TestItem("A", new []
        {
            new TestItem("B", new []
            {
                new TestItem("C"),
                new TestItem("D")
            } ),
        } );

        // Compute the number of children to each node in the tree
        var childrenByItem = new Dictionary<string, int>();
        MapChildren<TestItem, int>(test, i => i.Children, 
            (item, level, childResults) => (childrenByItem[item.Name] = childResults.Sum(c => c)) + 1);

        Assert.AreEqual(3, childrenByItem["A"]);
        Assert.AreEqual(2, childrenByItem["B"]);
        Assert.AreEqual(0, childrenByItem["C"]);
        Assert.AreEqual(0, childrenByItem["D"]);

        // Compute the "Hierarchy Level", i.e. maximal distance to a leaf node, for each node
        var levelByItem = new Dictionary<string, int>();
        Tree.MapChildren<TestItem, int>(test, i => i.Children,
            (item, level, childResults) => levelByItem[item.Name] = childResults.DefaultIfEmpty(-1).Max() + 1);

        Assert.AreEqual(2, levelByItem["A"]);
        Assert.AreEqual(1, levelByItem["B"]);
        Assert.AreEqual(0, levelByItem["C"]);
        Assert.AreEqual(0, levelByItem["D"]);
    }

【讨论】:

  • 这是什么?我不明白你的意思,什么是 getChildren(self)???
  • @Arunprasanth K V 添加了一些解释和更好的示例。
  • 感谢您的解释。但似乎类的结构和你的 TestItem 是不同的。您可以使用问题中的类 NavBarItem 吗
【解决方案2】:

我们可以使用下面的方法来获取层次列表的深度

public static IEnumerable<Tuple<int, T>> FindDepthOfTreeNode<T>(T root, Func<T, IEnumerable<T>> children)
        {
            var stack = new Stack<Tuple<int, T>>();

            stack.Push(Tuple.Create(1, root));

            while (stack.Count > 0)
            {
                var node = stack.Pop();

                foreach (var child in children(node.Item2))
                {
                    stack.Push(Tuple.Create(node.Item1 + 1, child));
                }
                yield return node;
            }
        }

然后像下面这样使用它

int depth = menuItem.Children == null ? 0 : menuItem.Children
                                .SelectMany(y => FindDepthOfTreeNode(y, xs => xs.Children ??
                                Enumerable.Empty<NavBarItem>()))
                                .Max(xs => xs.Item1);

为了获取分层列表节点中的子节点总数,我们可以使用以下方法。

public static int GetChildCountFromTree(this NavBarItem obj)
        {
            var queue = new Queue<NavBarItem>();
            queue.Enqueue(obj); // Note that you can add first object in the queue constructor

            var result = 0;

            while (queue.Any())
            {
                var current = queue.Dequeue();
                result++;
                if (null != current.Children)
                {
                    foreach (NavBarItem inner in current.Children)
                    {
                        queue.Enqueue(inner);
                    }

                    current.Last =  true;
                }
            }

            return result;
        }

我们可以像下面这样使用它

ourHirarchyNode.GetChildCountFromTree();

【讨论】:

    【解决方案3】:

    让我知道这是否适合你:

    var lookup = items.ToLookup(x => x.ParentId);
    
    (int children, int level) Recurse(int? parentId)
    {
        var r = lookup[parentId].Select(x => Recurse(x.Id)).ToArray();
        return r.Any() ? (r.Sum(x => x.children + 1), r.Max(x => x.level) + 1) : (0, 0);
    }
    

    我的Recurse 方法是本地方法。

    这是我的测试代码:

    void Main()
    {
        var items = new[]
        {
            new NavBarItem() {ParentId = null, Id = 10, Name = "a"},
            new NavBarItem() {ParentId = null, Id = 33, Name = "aa"},
            new NavBarItem() {ParentId = 10 , Id = 11, Name = "aaa"},
            new NavBarItem() {ParentId = 10, Id = 12, Name = "aaaa"},
            new NavBarItem() {ParentId = 11, Id = 13, Name = "aaaaa"},
            new NavBarItem() {ParentId = 56 ,Id = 14, Name = "aas"},
            new NavBarItem() {ParentId = 78 , Id = 15, Name = "adss"},
            new NavBarItem() {ParentId = 99 , Id = 16, Name = "ader"},
        };
    
        var lookup = items.ToLookup(x => x.ParentId);
    
        (int children, int level) Recurse(int? parentId)
        {
            var r = lookup[parentId].Select(x => Recurse(x.Id)).ToArray();
            return r.Any() ? (r.Sum(x => x.children + 1), r.Max(x => x.level) + 1) : (0, 0);
        }
    
        var parents = new int?[] { null, 10, 11, 56, 78, 99 };
    
        Console.WriteLine(
            String.Join(
                Environment.NewLine,
                parents
                    .Select(p => new { p, r = Recurse(p) })
                    .Select(x => $"{x.p} => {x.r.children}, {x.r.level}")));
    }
    
    public class NavBarItem
    {
        public int? Id { get; set; }
        public int? ParentId { get; set; }
        public string Name { get; set; }
    }
    

    我得到的结果是:

     => 5, 3
    10 => 3, 2
    11 => 1, 1
    56 => 1, 1
    78 => 1, 1
    99 => 1, 1
    

    【讨论】:

    • 感谢您的支持。我会检查并告诉你,但我们是否需要获取 parents var parents = new int 中所有项目的 id?[] { null, 10, 11, 56, 78, 99 };没有这个还有其他选择吗?因为我这里确实有不止一个父母,其 id 为空。从逻辑上讲它是不正确的,但这是一个旧的数据库结构,我们目前无法更改。我想我可能必须遍历所有父 ID 为 null 的项目并执行您在此处提到的方法
    • @ArunprasanthKV - 不,var parents = new int?[] { null, 10, 11, 56, 78, 99 }; 只是为了演示如何调用代码。您可以将其称为 Recurse(null)Recurse(101)
    猜你喜欢
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多