我正在做一个递归的LINQ函数,如tis问题所述: Simulating CTE recursion in C#
我的代码如下:
private static IEnumerable<KeyValuePair<int, int>> getNet(List<DataRow> list, int? leader, int level)
{
return list
.Where(x => x.Field<int?>("LeaderID") == leader)
.SelectMany(x =>
new[] {
new KeyValuePair<int, int>(x.Field<int>("RepID"), level)
}.Concat(getNet(list, x.Field<int>("RepID"), level+ 1))
);
}
我想在再次输入该函数之前验证父母是否有孩子,因为每个孩子都会再次被评估并且会花费很多时间。
即 家长A有5000个孩子,但只有5个孩子有孩子,我需要一些东西来验证A的孩子在为所有孩子执行这个功能之前是否有孩子。
谢谢!
答案 0 :(得分:3)
因此,早些时候对孩子进行测试确实无法帮助你。从概念上讲,你仍然在做同样的工作。如果每个递归调用一次处理两个深度,而不是一个,则会使方法本身变得非常复杂,只要有孩子就会复制第二个深度的工作,即使没有孩子也会获得非常非常小的工作。该方法的昂贵部分是通过子列表的线性搜索,而不是在开始搜索的方法调用中。
为了提高性能,您需要多次停止通过非常大的列表进行线性搜索。幸运的是,这很容易做到。只需在方法开始时为每个父级创建一次查找。
var lookup = list.ToLookup(x => x.Field<int?>("LeaderID"));
现在,从概念上讲,你要做的是遍历一棵树。我们可以从一个能够遍历任何树的通用“遍历”方法开始(使用非递归实现,以提高性能):
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);
}
}
}
现在我们已经拥有了这些,我们可以使用查找来有效地获取每个节点的所有子节点,从而大大改进了实现。当然,如果你不需要每个节点的深度,它会更漂亮,但它仍然没有那么糟糕。
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)));
如果您不需要知道每个节点的深度,可以将所有这些简化为:
var lookup = list.ToLookup(
row => row.Field<int?>("LeaderID"),
row => row.Field<int>("RepID"));
return Traverse(lookup[leader], node => lookup[node]);