Firstly, you are absolutely right; if the graph has n nodes of average depth d, then naive nested iterators give a solution that is O (n * d) in time, and O (d) on the stack. If d is a large part of n, then this can become an O (n 2 ) algorithm, and if d is large, you can completely pop the stack.
If you are interested in analyzing the performance of nested iterators, see the previous post by the Wes Dyer C # compiler developer:
http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx
The dasblinkenlight solution is a variant of the standard approach. I usually write a program as follows:
public static IEnumerable<T> Traverse<T>( T root, Func<T, IEnumerable<T>> children) { var stack = new Stack<T>(); stack.Push(root); while(stack.Count != 0) { T item = stack.Pop(); yield return item; foreach(var child in children(item)) stack.Push(child); } }
And if you have several roots:
public static IEnumerable<T> Traverse<T>( IEnumerable<T> roots, Func<T, IEnumerable<T>> children) { return from root in roots from item in Traverse(root, children) select item ; }
Now note that the crawl is not what you want if you have a very interconnected graph or a cyclic graph! If you have a chart with down arrows:
A / \ B-->C \ / D
then bypass A, B, D, C, D, C, D. If you have a cyclical or interconnected schedule, then you want this transitive closure.
public static IEnumerable<T> Closure<T>( T root, Func<T, IEnumerable<T>> children) { var seen = new HashSet<T>(); var stack = new Stack<T>(); stack.Push(root); while(stack.Count != 0) { T item = stack.Pop(); if (seen.Contains(item)) continue; seen.Add(item); yield return item; foreach(var child in children(item)) stack.Push(child); } }
This change only yields items that were not previously received.
I am also not very good at recursion.
I wrote a series of articles on ways to eliminate recursion and recursive programming in general. If this topic interests you, see:
http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/
In particular:
http://blogs.msdn.com/b/ericlippert/archive/2005/08/01/recursion-part-two-unrolling-a-recursive-function-with-an-explicit-stack.aspx
http://blogs.msdn.com/b/ericlippert/archive/2005/08/04/recursion-part-three-building-a-dispatch-engine.aspx
http://blogs.msdn.com/b/ericlippert/archive/2005/08/08/recursion-part-four-continuation-passing-style.aspx