LINQ递归函数?

LINQ递归函数?,linq,c#-4.0,recursion,Linq,C# 4.0,Recursion,让我们以这个n层深层结构为例: public class SomeItem { public Guid ID { get;set; } public string Name { get; set; } public bool HasChildren { get;set; } public IEnumerable<SomeItem> Children { get; set; } } 公共类SomeItem { 公共Guid ID{get;se

让我们以这个n层深层结构为例:

public class SomeItem 
{
     public Guid ID { get;set; }
     public string Name { get; set; }
     public bool HasChildren { get;set; }
     public IEnumerable<SomeItem> Children { get; set; }
}
公共类SomeItem
{
公共Guid ID{get;set;}
公共字符串名称{get;set;}
公共bool有子项{get;set;}
公共IEnumerable子项{get;set;}
}
如果我希望通过ID(结构中的任何位置)获取特定项,是否有一些LINQ优点可用于在单个语句中轻松获取它,或者我是否必须使用以下递归函数:

   private SomeItem GetSomeItem(IEnumerable<SomeItem> items, Guid ID)
    {
        foreach (var item in items)
        {
            if (item.ID == ID)
            {
                return item;
            }
            else if (item.HasChildren)
            {
                return GetSomeItem(item.Children, ID);
            }
        }
        return null;
    }
private-SomeItem-GetSomeItem(IEnumerable-items,Guid-ID)
{
foreach(项目中的var项目)
{
如果(item.ID==ID)
{
退货项目;
}
else if(项HasChildren)
{
返回GetSomeItem(item.Children,ID);
}
}
返回null;
}
LINQ并不能很好地“执行”递归。你的解决方案似乎很合适——尽管我不确定是否真的需要孩子。。。为什么不为没有子项的项使用空列表

另一种方法是编写一个
子代和self
方法,该方法将返回所有子代(包括项本身),如下所示

// Warning: potentially expensive!
public IEnumerable<SomeItem> DescendantsAndSelf()
{
    yield return this;
    foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf()))
    {
        yield return item;
    }
}
//警告:可能很贵!
公共IEnumerable后代和self()
{
收益回报这一点;
foreach(Children.SelectMany中的var项(x=>x.degenantsandself())
{
收益回报项目;
}
}
但是,如果树很深,则效率非常低,因为每个项都需要“通过”其祖先的所有迭代器。Wes Dyer提供了一个更高效的实现


无论如何,如果您有这样一个方法(不管它是如何实现的),您可以使用一个普通的“where”子句来查找一个项(或First/FirstOrDefault等)。

虽然有一些扩展方法可以在LINQ中启用递归(可能看起来像您的函数),但没有现成的方法

这些扩展方法的示例可以找到或找到

我想说你的功能很好。

希望这有帮助

public static IEnumerable<Control> GetAllChildControls(this Control parent)
{
  foreach (Control child in parent.Controls)
  {
    yield return child;

    if (child.HasChildren)
    {
      foreach (Control grandChild in child.GetAllChildControls())
        yield return grandChild;
    }
  }
}
公共静态IEnumerable GetAllChildControls(此控件父控件)
{
foreach(父控件中的控件子控件)
{
退换子女;
if(child.HasChildren)
{
foreach(在child.GetAllChildControls()中控制孙子对象)
收益回报孙子;
}
}
}

这里有一个没有递归的。这避免了通过几层迭代器的成本,因此我认为这与迭代器的效率一样

    public static IEnumerable<T> IterateTree<T>(this T root, Func<T, IEnumerable<T>> childrenF)
    {
        var q = new List<T>() { root };
        while (q.Any())
        {
            var c = q[0];
            q.RemoveAt(0);
            q.AddRange(childrenF(c) ?? Enumerable.Empty<T>());
            yield return c;
        }
    }

记住这一点很重要,您不需要使用LINQ做任何事情,也不需要默认使用递归。当您使用数据结构时,有一些有趣的选项。下面是一个简单的展平函数,以防有人感兴趣

    public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items)
    {
        if (items == null || items.Count() == 0) return new List<SomeItem>();

        var result = new List<SomeItem>();
        var q = new Queue<SomeItem>(collection: items);

        while (q.Count > 0)
        {
            var item = q.Dequeue();
            result.Add(item);

            if (item?.Children?.Count() > 0)
                foreach (var child in item.Children)
                    q.Enqueue(child);
        }

        return result;
    }
公共静态IEnumerable展平(IEnumerable项)
{
如果(items==null | | items.Count()==0)返回新列表();
var result=新列表();
var q=新队列(集合:项);
而(q.Count>0)
{
var item=q.Dequeue();
结果.添加(项目);
如果(项?.Children?.Count()>0)
foreach(item.Children中的变量child)
q、 排队(儿童);
}
返回结果;
}

您真的需要HasChildren属性吗?不需要,只需要一个助手,这样它读起来会更好:)-Children.Count>0@The_ButcherChildren.Any():)生成的迭代器的数量不是节点总数的函数,而是树深度的函数吗?(每个节点都会调用DegenantsAndSelf)。如果你反复调用DegenantsAndSelf(),它会再次枚举整个树还是LINQ会保留结果?@屠夫:LINQ在这里没有做任何神奇的事情。它不会为你缓存任何东西。当然,您可以自己调用ToList来生成缓存副本。@Amnon:True,生成的迭代器的数量取决于节点的数量,但每个项在传播到顶部迭代器时需要通过的迭代器的数量取决于树的深度。因此,深层树的迭代效率仍然低于浅层树。我正要问一个关于递归linq的新问题,这个问题的答案(后代和自我)非常好(与SelectMany结合)。谢谢这可以写为扩展方法吗?谢谢-类似于,如果您使用队列而不是列表来实现函数,它可能会更清晰。
var c=q[0];q、 RemoveAt(0)
实际上是
Queue.Dequeue()
方法与
.Count()
(可枚举)的潜在更低效的实现,与
.Count
(列表)相反,您仍然需要每次查看列表。如果无法避免这种情况,则可以从
.Any()
    public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items)
    {
        if (items == null || items.Count() == 0) return new List<SomeItem>();

        var result = new List<SomeItem>();
        var q = new Queue<SomeItem>(collection: items);

        while (q.Count > 0)
        {
            var item = q.Dequeue();
            result.Add(item);

            if (item?.Children?.Count() > 0)
                foreach (var child in item.Children)
                    q.Enqueue(child);
        }

        return result;
    }