Я нашел этот вопрос, когда искал и думал о похожем решении - в моем случае я создал эффективный IEnumerable<Control>
для элементов управления ASP.NET UI. Рекурсивный yield
, который у меня был, быстрый, но я знал, что это может привести к дополнительным расходам, поскольку чем глубже структура управления, тем дольше он может занять. Теперь я знаю, что это O (n log n).
Решение, приведенное здесь, дает некоторый ответ, но, как обсуждалось в комментариях, оно меняет порядок (который ОП не заботил). Я понял, что для сохранения порядка, заданного ОП и по мере необходимости, не будут работать ни простые Queue
(как использовал Джон), ни Stack
, поскольку сначала будут получены все родительские объекты, а затем любые потомки после них ( или наоборот).
Чтобы решить эту проблему и сохранить порядок, я понял, что решение будет просто поместить Enumerator
в Stack
. Если использовать оригинальный вопрос ОП, это будет выглядеть так:
public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> subjects, Func<T, IEnumerable<T>> selector)
{
if (subjects == null)
yield break;
var stack = new Stack<IEnumerator<T>>();
stack.Push(subjects.GetEnumerator());
while (stack.Count > 0)
{
var en = stack.Peek();
if (en.MoveNext())
{
var subject = en.Current;
yield return subject;
stack.Push(selector(subject).GetEnumerator());
}
else
{
stack.Pop();
}
}
}
Я использую stack.Peek
здесь, чтобы избежать необходимости помещать один и тот же перечислитель обратно в стек, так как это, вероятно, будет более частой операцией, ожидая, что перечислитель предоставит более одного элемента.
При этом создается то же количество перечислителей, что и в рекурсивной версии, но, скорее всего, будет меньше новых объектов, чем при помещении всех объектов в очередь или стек и продолжении добавления любых объектов-потомков. Это время O (n), поскольку каждый перечислитель стоит сам по себе (в рекурсивной версии неявный вызов одного MoveNext
выполняет MoveNext
на дочерних перечислителях до текущей глубины в стеке рекурсии).