今天我打算实现一种方法来遍历任意深度的图并将其展平为单个可枚举。相反,我先做了一点搜索,发现了这个:
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> enumerable, Func<T, IEnumerable<T>> recursivePropertySelector)
{
foreach (T item in enumerable)
{
yield return item;
IEnumerable<T> seqRecurse = recursivePropertySelector(item);
if (seqRecurse == null) continue;
foreach (T itemRecurse in Traverse(seqRecurse, recursivePropertySelector))
{
yield return itemRecurse;
}
}
}
从理论上讲,这看起来不错,但在实践中,我发现它的性能比使用等效的手写代码(随着情况的出现)来遍历图表并执行任何需要做的事情要差得多。我怀疑这是因为在这种方法中,对于它返回的每个项目,堆栈都必须展开到任意深度。
我还怀疑如果消除递归,这种方法会更有效地运行。我也恰好不擅长消除递归。
有谁知道如何重写这个方法来消除递归?
谢谢你的帮助。
编辑:非常感谢所有详细的回复。我尝试对原始解决方案与 Eric 的解决方案进行基准测试,而不是使用枚举器方法,而是使用 aa lambda 递归遍历,奇怪的是,lambda 递归比其他两种方法中的任何一种都要快得多。
class Node
{
public List<Node> ChildNodes { get; set; }
public Node()
{
ChildNodes = new List<Node>();
}
}
class Foo
{
public static void Main(String[] args)
{
var nodes = new List<Node>();
for(int i = 0; i < 100; i++)
{
var nodeA = new Node();
nodes.Add(nodeA);
for (int j = 0; j < 100; j++)
{
var nodeB = new Node();
nodeA.ChildNodes.Add(nodeB);
for (int k = 0; k < 100; k++)
{
var nodeC = new Node();
nodeB.ChildNodes.Add(nodeC);
for(int l = 0; l < 12; l++)
{
var nodeD = new Node();
nodeC.ChildNodes.Add(nodeD);
}
}
}
}
nodes.TraverseOld(node => node.ChildNodes).ToList();
nodes.TraverseNew(node => node.ChildNodes).ToList();
var watch = Stopwatch.StartNew();
nodes.TraverseOld(node => node.ChildNodes).ToList();
watch.Stop();
var recursiveTraversalTime = watch.ElapsedMilliseconds;
watch.Restart();
nodes.TraverseNew(node => node.ChildNodes).ToList();
watch.Stop();
var noRecursionTraversalTime = watch.ElapsedMilliseconds;
Action<Node> visitNode = null;
visitNode = node =>
{
foreach (var child in node.ChildNodes)
visitNode(child);
};
watch.Restart();
foreach(var node in nodes)
visitNode(node);
watch.Stop();
var lambdaRecursionTime = watch.ElapsedMilliseconds;
}
}
其中 TraverseOld 是原始方法, TraverseNew 是 Eric 的方法,显然 lambda 是 lambda。
在我的机器上,TraverseOld 需要 10127 毫秒,TraverseNew 需要 3038 毫秒,lambda 递归需要 1181 毫秒。
这是典型的枚举器方法(带有收益返回)可能需要 3 倍的时间而不是立即执行吗?还是这里发生了其他事情?