今日、私は、任意の深いグラフをトラバースし、単一の列挙型にフラット化するメソッドを実装しようとしていました。代わりに、私は最初少し検索を行なったし、これを見つけた:これはよさそうだが、実際には、私はそれが(状況に応じて)同等の手で書かれたコードを使用するよりも悪い大幅行い発見した理論的にはLINQを使った効率的なグラフトラバーサル - 再帰の排除
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;
}
}
}
グラフを通って実行する必要があるものはすべて実行します。これは、このメソッドでは、返されるすべてのアイテムについて、スタックが任意の深いレベルに巻き戻されなければならないためです。
また、この方法は、再帰が排除された場合、より効率的に実行されると考えられます。私はまた、再帰を排除することにはあまりうまくいきません。
再帰を排除するためにこのメソッドを書き換える方法を知っている人はいますか?
ありがとうございました。
EDIT: すべての詳細な対応についてありがとうございます。私は元の解とベンチマークを比較しようとしましたが、エリックの解法と列挙法を使用せず、代わりにラムダで巡回し、奇妙なことに、ラムダ再帰は他の2つの方法よりも大幅に高速です。
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;
}
}
ここで、TraverseNldは元の方法ですが、TraverseNewはEricの方法であり、明らかにラムダはラムダです。
私のマシンでは、TraverseOldは10127 ms、TraverseNewは3038 ms、ラムダ再帰は1181 msです。
これは典型的なもので、列挙型メソッド(yield returns with)は即時実行に比べて3倍長くかかる可能性がありますか?それとも何か別のことが起こっていますか?
は、それが見えますすべてのレベルで、すなわち素朴なフィボナッチ 'f(x)= f(x-1)+ f(x-2) 'となる。 – mellamokb
最後のバージョンは、最初の2つと同じ量の作業の近くでは行っていません。特に、リストを割り当てたり、配列から配列に項目をコピーしたりすることはありません。あなたが国勢調査であると想像してみてください。単に家から家まで歩き回り、気にしないで、情報を書き留めたら、あなたの仕事はずっと速くなります。 –
私はTraverseOldとTraverseNewを3年後に見つけることができますか? – user1756694