【问题标题】:LINQ recursion function?LINQ递归函数?
【发布时间】:2011-06-16 09:37:22
【问题描述】:

我们以这个 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; }
}

如果我想通过 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;
    }

【问题讨论】:

  • 你真的需要 HasChildren 属性吗?
  • 不是真的,只是一个帮手,所以它读起来更好:) - Children.Count > 0
  • @The_Butcher Children.Any() :)

标签: linq c#-4.0 recursion


【解决方案1】:

LINQ 并不能很好地“执行”递归。您的解决方案似乎是合适的 - 虽然我不确定 HasChildren 是否真的需要......为什么不对没有孩子的项目使用空列表?

另一种方法是编写一个DescendantsAndSelf 方法,该方法将返回所有的后代(包括项目本身),类似这样;

// Warning: potentially expensive!
public IEnumerable<SomeItem> DescendantsAndSelf()
{
    yield return this;
    foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf()))
    {
        yield return item;
    }
}

但是,如果树很深,最终效率会非常低,因为每个项目都需要“通过”其祖先的所有迭代器。 Wes Dyer 有blogged about this,展示了更高效的实现。

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

【讨论】:

  • 生成的迭代器数量不是节点总数的函数,而不是树深度的函数吗? (为每个节点调用 DescendantsAndSelf)。
  • 如果您反复调用 DescendantsAndSelf(),它会再次枚举整个树还是 LINQ 会保留结果?
  • @The_Butcher:LINQ 在这里没有做任何神奇的事情。它不会为您缓存任何内容。当然,你也可以调用 ToList 自己生成一个缓存副本。
  • @Amnon:没错,生成的迭代器的数量是基于节点的数量 - 但是每个项目在传播到顶部迭代器时需要通过的迭代器数量取决于那个树。因此,深度树的迭代效率仍然低于浅树。将进行编辑以使其更清晰。
  • 我正要问一个关于递归 linq 的新问题,这个答案(DescendantsAndSelf 技巧)很棒。(结合 SelectMany)。谢谢。这可以写成扩展方法吗?
【解决方案2】:

虽然有一些扩展方法可以在 LINQ 中启用递归(并且可能看起来像您的函数),但没有提供开箱即用的方法。

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

我会说你的功能很好。

【讨论】:

    【解决方案3】:

    希望对你有帮助

    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;
        }
      }
    }
    

    【讨论】:

    【解决方案4】:

    这是一个没有递归的。这避免了通过几层迭代器的成本,所以我认为它的效率差不多。

        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;
            }
        }
    

    像这样调用:

                var subtree = root.IterateTree(x => x. Children).ToList();
    

    【讨论】:

    • 如果您使用队列而不是列表来实现该功能,也许应该更清楚。 var c = q[0]; q.RemoveAt(0); 确实是 Queue.Dequeue() 方法的一个潜在的低效实现
    【解决方案5】:

    重要的是要记住,您不需要使用 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;
        }
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-01
    • 2018-07-05
    • 2010-12-23
    • 2019-12-04
    • 1970-01-01
    • 2010-12-25
    相关资源
    最近更新 更多