Find all descendants in self-referencing (parent-child) hierarchical tree

前端 未结 1 650
说谎
说谎 2021-01-06 18:13

This is similar to the question (Finding parents in a tree hierarchy for a given child LINQ (lambda expression)). However, instead of finding all ancestors, I need to find a

相关标签:
1条回答
  • 2021-01-06 18:51

    I am modifying Yacoub's method but only managed to get all descendants in one branch

    This is because of this line:

    var child = allRoles.FirstOrDefault(x => x.ParentId == role.Id);
    

    While it might have been appropriate for finding a single parent, it's not appropriate for finding multiple children.

    But you don't need recursive iterators and multiple iterations over the allRoles list. You can create a fast lookup structure using ToLookup extension method and then perform iterative DFS like this:

    private static IEnumerable<UserRole> FindAllChildren(List<UserRole> allRoles, UserRole role)
    {
        var childrenByParentId = allRoles.ToLookup(r => r.ParentId);
        var stack = new Stack<IEnumerator<UserRole>>();
        var e = childrenByParentId[role != null ? role.Id : (int?)null].GetEnumerator();
        try
        {
            while (true)
            {
                while (e.MoveNext())
                {
                    yield return e.Current;
                    stack.Push(e);
                    e = childrenByParentId[e.Current.Id].GetEnumerator();
                }
                if (stack.Count == 0) break;
                e.Dispose();
                e = stack.Pop();
            }
        }
        finally
        {
            e.Dispose();
            while (stack.Count > 0) stack.Pop().Dispose();
        }
    }
    

    An even better approach would be (following the DRY principle) to utilize the generic tree helper method from How to flatten tree via LINQ?:

    public static class TreeUtils
    {
        public static IEnumerable<T> Expand<T>(
                this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
        {
            var stack = new Stack<IEnumerator<T>>();
            var e = source.GetEnumerator();
            try
            {
                while (true)
                {
                    while (e.MoveNext())
                    {
                        var item = e.Current;
                        yield return item;
                        var elements = elementSelector(item);
                        if (elements == null) continue;
                        stack.Push(e);
                        e = elements.GetEnumerator();
                    }
                    if (stack.Count == 0) break;
                    e.Dispose();
                    e = stack.Pop();
                }
            }
            finally
            {
                e.Dispose();
                while (stack.Count != 0) stack.Pop().Dispose();
            }
        }
    }
    

    like this:

    private static IEnumerable<UserRole> FindAllChildren(List<UserRole> allRoles, UserRole role)
    {
        var childrenByParentId = allRoles.ToLookup(r => r.ParentId);
        return childrenByParentId[role != null ? role.Id : (int?)null].Expand(r => childrenByParentId[r.Id]);
    }
    
    0 讨论(0)
提交回复
热议问题