不知道如何调用它,但假设您有一个类如下所示:
class Person
{
    public string Name;
    public IEnumerable<Person> Friends;
}然后,您有了一个person,并且您希望递归地“展开”这个结构,这样您就得到了一个没有重复的所有人员的列表。
你会怎么做?我已经做了一些似乎正在起作用的事情,但是我很好奇别人会怎么做,特别是如果Linq有内置的东西,你可以用一种聪明的方法来解决这个小问题:)
这是我的解决方案:
public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> subjects, Func<T, IEnumerable<T>> selector)
{
    // Stop if subjects are null or empty
    if(subjects == null)
        yield break;
    // For each subject
    foreach(var subject in subjects)
    {
        // Yield it
        yield return subject;
        // Then yield all its decendants
        foreach (var decendant in SelectRecursive(selector(subject), selector))
            yield return decendant;
    }
}会用到这样的东西:
var people = somePerson.SelectRecursive(x => x.Friends);发布于 2010-01-06 10:44:11
我不相信LINQ里面有什么东西能做到这一点。
像这样递归地这样做有一个问题--你最终会创建大量的迭代器。如果这棵树很深的话,这可能是相当低效的。韦斯·戴尔和埃里克·利珀特都曾在博客上提到过这一点。
您可以通过删除直接递归来消除这种低效率。例如:
public static IEnumerable<T> SelectRecursive<T>(this IEnumerable<T> subjects,
    Func<T, IEnumerable<T>> selector)
{
    if (subjects == null)
    {
        yield break;
    }
    Queue<T> stillToProcess = new Queue<T>(subjects);
    while (stillToProcess.Count > 0)
    {
        T item = stillToProcess.Dequeue();
        yield return item;
        foreach (T child in selector(item))
        {
            stillToProcess.Enqueue(child);
        }
    }
}这也将改变迭代顺序--它变成广度优先,而不是深度优先;重写它仍然是深度优先--这是很棘手的。我还将其更改为不使用Any() --这个修改后的版本不会对任何序列进行多次评估,这在某些场景中是很方便的。这确实有一个问题,请注意-它将需要更多的内存,因为排队。我们也许可以通过存储迭代器队列而不是条目来缓解这一问题,但我不能马上确定.这肯定会更复杂。
有一点要注意(当我查找博客文章时,ChrisW也注意到了)--如果你的朋友列表中有任何循环(如果A有B,B有A),那么你就会永远回忆起来。
发布于 2012-01-12 02:46:54
在我寻找和考虑类似的解决方案时,我发现了这个问题--在我的例子中,为ASP.NET UI控件创建了一个高效的ASP.NET。我所拥有的递归yield是快速的,但我知道这可能会带来额外的成本,因为控制结构越深,所需的时间就越长。现在我知道这是O(n log n)。
这里给出的解决方案提供了一些答案,但正如注释中所讨论的,它确实改变了顺序( OP并不关心这个顺序)。我意识到,为了保持OP给出的顺序和我所需要的顺序,简单的Queue (使用Jon )和Stack都不能工作,因为所有父对象都会先生成,然后再生成子对象(反之亦然)。
为了解决这个问题并保持我意识到的顺序,解决方案只是将Enumerator本身放在一个Stack上。要使用OPs最初的问题,应该如下所示:
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().Dispose();
        }
    }
}我在这里使用stack.Peek来避免将相同的枚举数推回堆栈,因为这可能是更频繁的操作,期望枚举器提供多个项。
这将创建与递归版本相同的枚举数,但可能比将所有主题放在队列或堆栈中并继续添加任何子代主题更少。这是O(n)时间,因为每个枚举数都是独立的(在递归版本中,对一个MoveNext的隐式调用在递归堆栈中的当前深度对子枚举数执行MoveNext )。
发布于 2014-09-04 03:51:54
以下是一个实现:
https://stackoverflow.com/questions/2012274
复制相似问题