C# 如何";展开;a「;递归的;结构

C# 如何";展开;a「;递归的;结构,c#,recursion,ienumerable,C#,Recursion,Ienumerable,不知道如何调用它,但假设您有一个类似于以下内容的类: class Person { public string Name; public IEnumerable<Person> Friends; } var people = somePerson.SelectRecursive(x => x.Friends); public static IEnumerable<T> SelectRecursive<T>(this IEnumerab

不知道如何调用它,但假设您有一个类似于以下内容的类:

class Person
{
    public string Name;
    public IEnumerable<Person> Friends;
}
var people = somePerson.SelectRecursive(x => x.Friends);
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();
        }
    }
}

我不相信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);
        }
    }
}
public static IEnumerable SelectRecursive(此IEnumerable主题,
Func选择器)
{
if(subjects==null)
{
屈服断裂;
}
Queue stillToProcess=新队列(主题);
while(stillToProcess.Count>0)
{
T item=stillToProcess.Dequeue();
收益回报项目;
foreach(选择器中的T子项(项目))
{
stillToProcess.Enqueue(子级);
}
}
}
这也将改变迭代顺序——它变为广度优先而不是深度优先;将其改写为深度优先是一件棘手的事情。我还将其更改为不使用
Any()
——此修订版不会对任何序列进行多次评估,这在某些情况下非常方便。请注意,这确实有一个问题——由于排队,它将占用更多内存。我们可能可以通过存储一个迭代器队列而不是项来缓解这一问题,但我不能马上确定。。。这肯定会更加复杂


有一点需要注意(ChrisW在我查看博客帖子时也提到了:)-如果你的好友列表中有任何循环(即如果A有B,B有A),那么你将永远递归。

你也可以使用这样的非递归方法:

  HashSet<Person> GatherAll (Person p) {
     Stack<Person> todo = new Stack<Person> ();
     HashSet<Person> results = new HashSet<Person> ();
     todo.Add (p); results.Add (p);
     while (todo.Count > 0) {
        Person p = todo.Pop (); 
        foreach (Person f in p.Friends) 
           if (results.Add (f)) todo.Add (f);
     }
     return results;
  }
HashSet-GatherAll(个人p){
Stack todo=新堆栈();
HashSet results=newhashset();
todo.Add(p);results.Add(p);
而(todo.Count>0){
personp=todo.Pop();
foreach(p.Friends中的f人)
如果(results.Add(f))todo.Add(f);
}
返回结果;
}

这也应该正确处理循环。我从一个人开始,但是你可以很容易地扩展到一个列表

递归总是很有趣的。也许您可以将代码简化为:

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 || !subjects.Any())
        return Enumerable.Empty<T>();

    // Gather a list of all (selected) child elements of all subjects
    var subjectChildren = subjects.SelectMany(selector);

    // Jump into the recursion for each of the child elements
    var recursiveChildren = SelectRecursive(subjectChildren, selector);

    // Combine the subjects with all of their (recursive child elements).
    // The union will remove any direct parent-child duplicates.
    // Endless loops due to circular references are however still possible.
    return subjects.Union(recursiveChildren);
}
public静态IEnumerable SelectRecursive(此IEnumerable主题,Func选择器){
//如果主题为null或空,则停止
if(subjects==null | |!subjects.Any())
返回可枚举的.Empty();
//收集所有主题的所有(选定)子元素的列表
var subjectChildren=subjects.SelectMany(选择器);
//跳转到每个子元素的递归
var recursiveChildren=SelectRecursive(subjectChildren,selector);
//将主题与其所有(递归子元素)组合在一起。
//联合将删除任何直接的父子副本。
//然而,由于循环引用而产生的无休止的循环仍然是可能的。
返回主题。联合(儿童);
}
它将导致比原始代码更少的重复。但是,它们可能仍然是重复的,从而导致无休止的循环,联合将只阻止直接的父项-子项重复

项目的顺序将与您的不同:)


编辑:将最后一行代码更改为三条语句,并添加了更多文档。

使用聚合扩展名

    List<Person> persons = GetPersons(); 
    List<Person> result = new List<Person>(); 
    persons.Aggregate(result,SomeFunc);

    private static List<Person> SomeFunc(List<Person> arg1,Person arg2)
    {
    arg1.Add(arg2)
    arg1.AddRange(arg2.Persons);
    return arg1;
    }
List persons=GetPersons();
列表结果=新列表();
人员总数(结果,SomeFunc);
私有静态列表SomeFunc(列表arg1,人员arg2)
{
arg1.添加(arg2)
arg1.AddRange(arg2.Persons);
返回arg1;
}

我在寻找和思考类似的解决方案时发现了这个问题——在我的例子中,我为ASP.NET UI控件创建了一个高效的
IEnumerable
。我拥有的递归
yield
速度很快,但我知道这可能会有额外的成本,因为控制结构越深,所需的时间就越长。现在我知道这是O(n logn)

这里给出的解决方案提供了一些答案,但正如评论中所讨论的,它确实改变了顺序(OP并不关心)。我意识到,为了保持OP和我所需要的顺序,无论是简单的
队列
(Jon使用的)还是
堆栈
,都不会起作用,因为所有的父对象都会先生成,然后再生成子对象(反之亦然)

为了解决这个问题并保持顺序,我意识到解决方案只是将
枚举器本身放在
堆栈上。要使用OPs原始问题,它将如下所示:

class Person
{
    public string Name;
    public IEnumerable<Person> Friends;
}
var people = somePerson.SelectRecursive(x => x.Friends);
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();
        }
    }
}
public静态IEnumerable SelectRecursive(此IEnumerable主题,Func选择器)
{
if(subjects==null)
屈服断裂;
var stack=新堆栈();
stack.Push(subjects.GetEnumerator());
而(stack.Count>0)
{
var en=stack.Peek();
if(en.MoveNext())
{
var主体=当前的;
收益主体;
stack.Push(选择器(subject.GetEnumerator());
}
其他的
{
stack.Pop().Dispose();
}
}
}
我在这里使用了
stack.Peek
,以避免将同一个枚举器推回到堆栈中,因为这可能是更频繁的操作,期望枚举器提供多个项


这将创建与递归版本中相同数量的枚举数,但与将所有主题放入队列或堆栈并继续添加任何子主题相比,新对象可能更少。这是O(n)时间,因为每个枚举数都是独立的(在递归版本中,对一个
MoveNext
的隐式调用在子枚举数上执行
MoveNext
,直到递归堆栈中的当前深度)。

以下是一个实现:

  • 深度优先递归是否选择
  • 不需要双i