4

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 all descendants.

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

    private IEnumerable<UserRole> FindAllChildrenRecursively(List<UserRole> allRoles, UserRole role)
{
    var child = allRoles.FirstOrDefault(x => x.ParentId == role.Id);

    if (child == null)
        return Enumerable.Empty<UserRole>();

    return new[] { child }.Concat(FindAllChildrenRecursively(allRoles, child));
}
Community
  • 1
  • 1
corix010
  • 551
  • 8
  • 25

1 Answers1

7

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]);
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343