How to validate if a parent item has children in recursive LINQ function?

后端 未结 1 1517
独厮守ぢ
独厮守ぢ 2021-01-16 14:08

I\'m doing a recursive LINQ function as described in tis question: Simulating CTE recursion in C#

My code is as follows:

private static IEnumerable&l         


        
相关标签:
1条回答
  • 2021-01-16 14:35

    So testing the children earlier really isn't going to help you. You're still doing the same work, conceptually. If each recursive call handles two depths at once, instead of one, you're greatly complicating the method itself, duplicating the work of the second depth whenever there are children, and gaining very, very little even when there are no children. The expensive part of the method is in the linear search through the list for the children, not in the method call that starts the search.

    To improve performance you need to stop doing linear searches through a very large list many, many times. Fortunately, this is easy enough to do. Just create a lookup once, at the start of the method, of all children for each parent.

    var lookup = list.ToLookup(x => x.Field<int?>("LeaderID"));
    

    Now, what you're trying to do, conceptually, is traverse a tree. We can start out with a generalized "traverse" method capable of traversing any tree (using a non-recursive implementation, for performance improvements):

    public static IEnumerable<T> Traverse<T>(IEnumerable<T> source, 
        Func<T, IEnumerable<T>> childSelector)
    {
        var queue = new Queue<T>(source);
        while (queue.Any())
        {
            var item = queue.Dequeue();
            yield return item;
            foreach (var child in childSelector(item))
            {
                queue.Enqueue(child);
            }
        }
    }
    

    Now that we have these we can use the lookup to efficiently get all children for each node, greatly improving the implementation. Of course, it'd be prettier if you didn't also need the depth of each node, but it's still not that bad.

    var roots = lookup[leader]
        .Select(row => Tuple.Create(row.Field<int>("RepID"), 0));
    
    return Traverse(roots, node => lookup[node.Item1]
        .Select(row => Tuple.Create(row.Field<int>("RepID"), node.Item2 + 1)));
    

    If you don't need to know the depth of each node you can simplify all of that down to this:

    var lookup = list.ToLookup(
        row => row.Field<int?>("LeaderID"),
        row => row.Field<int>("RepID"));
    return Traverse(lookup[leader], node => lookup[node]);
    
    0 讨论(0)
提交回复
热议问题