【问题标题】:Linq extension method, how to find child in collection recursiveLinq扩展方法,如何在集合递归中找到孩子
【发布时间】:2011-04-04 22:17:31
【问题描述】:

我已经熟悉Linq,但对扩展方法了解甚少,希望有人能帮助我。

所以我有这个分层集合伪代码即:

class Product
  prop name
  prop type
  prop id
  prop List<Product> children

我有一个产品列表列出产品。

我有什么方法可以通过 id 使用扩展方法在此集合中查找产品?换句话说,我在层次结构中的某个地方需要一个项目。

【问题讨论】:

  • 你的意思是:productsList.Where(x => x.Id == yourId);?
  • 或 productsList.FirstOrDefault(x => x.Id == yourId);?这将返回一个对象,如果没有找到匹配的对象,则返回 null。
  • 不,我的意思是我需要同时查看 ProductsList 和 ProductList->Product->Children 这是我的问题,我可以用递归方法来做,但我想知道是否有可能用linq 扩展。

标签: c# linq recursion extension-methods hierarchy


【解决方案1】:

这是一个通用的解决方案,一旦找到匹配项,就会短路层次结构的遍历。

public static class MyExtensions
{
    public static T FirstOrDefaultFromMany<T>(
        this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector,
        Predicate<T> condition)
    {
        // return default if no items
        if(source == null || !source.Any()) return default(T);

        // return result if found and stop traversing hierarchy
        var attempt = source.FirstOrDefault(t => condition(t));
        if(!Equals(attempt,default(T))) return attempt;

        // recursively call this function on lower levels of the
        // hierarchy until a match is found or the hierarchy is exhausted
        return source.SelectMany(childrenSelector)
            .FirstOrDefaultFromMany(childrenSelector, condition);
    }
}

在你的情况下使用它:

var matchingProduct = products.FirstOrDefaultFromMany(p => p.children, p => p.Id == 27);

【讨论】:

  • 嗨,我对这段代码做了一个小改动,效果很好:) 我改变了这个 if(Equals(attempt,default(T))) return attempt; to if(Equals(attempt != null) return attempt; 它就像一个魅力谢谢大家的帮助。
  • @sushiBite 我认为它实际上应该是if(!Equals(attempt, default(T))) return attempt;,因为T 的默认值可能不是null(如果T 是一个值类型)。
【解决方案2】:

您可以使用这种扩展方法来展平您的树结构:

static IEnumerable<Product> Flatten(this IEnumerable<Product> source)
{
    return source.Concat(source.SelectMany(p => p.Children.Flatten()));
}

用法:

var product42 = products.Flatten().Single(p => p.Id == 42);

请注意,这可能不是很快。如果您反复需要通过 id 查找产品,请创建字典:

var dict = products.Flatten().ToDictionary(p => p.Id);

var product42 = dict[42];

【讨论】:

  • 很好,我喜欢这种 Flatten 方法。如果我没记错的话,它将迭代广度优先(编辑:我错了,这不是广度优先,但问题仍然相关)。这是否意味着如果产品是列表中的第一项,并且您使用 First 而不是 Single,它不会展平整个层次结构? linq 的延迟执行在这里会有所帮助吗?
  • 这看起来是一个不错的解决方案,但它忽略了子列表可能是null 的可能性。
  • @Bubblewrap:你是对的。如果你使用First,那么由于延迟执行,Flatten 只会在需要的时候变平。
【解决方案3】:

我只是重构 dtb 的解决方案以使其更通用。试试这个扩展方法:

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T>
{
    return source.SelectMany(x => (recursion(x) != null && recursion(x).Any()) ? recursion(x).Flatten(recursion) : null)
                 .Where(x => x != null);
}

你可以这样使用它:

productList.Flatten(x => x.Children).Where(x => x.ID == id);

【讨论】:

    【解决方案4】:
    static IEnumerable<Product> FindProductById(this IEnumerable<Product> source, int id) 
    {
        return source.FirstOrDefault(product => product.Id = id) ?? source.SelectMany(product => product.Children).FindProductById(id);
    }
    

    【讨论】:

      【解决方案5】:

      使用产量优化所需枚举的替代解决方案。

      public static IEnumerable<T> SelectManyRecursive<T>(
          this IEnumerable<T> source,
          Func<T, IEnumerable<T>> childrenSelector)
      {
          if (source == null)
              throw new ArgumentNullException("source");
      
          foreach (var i in source)
          {
              yield return i;
              var children = childrenSelector(i);
              if (children != null)
              {
                  foreach (var child in SelectManyRecursive(children, childrenSelector))
                  {
                      yield return child;
                  }
              }
          }
      }
      

      然后您可以通过调用 FirstOrDefault 之类的方法找到匹配项:

          var match = People.SelectManyRecursive(c => c.Children)
                            .FirstOrDefault(x => x.Id == 5);
      

      【讨论】:

        【解决方案6】:

        如果您想“子迭代”并在产品列表中找到一个子项:

        List<Product>
            Product
               Child
               Child
               Child
               Child
            Product
               Child
               Child *find this one
               Child
        

        您可以使用现有的SelectMany 扩展方法。 SelectMany 可用于“扁平化”两级层次结构。

        这是对 SelectMany 的一个很好的解释:http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

        你的语法应该是这样的:

        List<Product> p = GetProducts(); //Get a list of products
        var child = from c in p.SelectMany(p => p.Children).Where(c => c.Id == yourID);
        

        【讨论】:

        • 嗯,这很好,但是每个产品的层次结构和深度为 x 层,因此产品 A 可以有 3 个孩子、5 个孙子和 100 个孙孙,而产品可能只能有 1 个孩子而没有孙子孩子,我没有办法知道。如果我正确理解这一点,我将不得不对层次结构的每个级别使用 SelectMany() ?
        • 您可以根据需要将 SelectMany 链接在一起以达到您想要的级别。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-19
        • 2010-10-06
        • 2010-11-02
        • 1970-01-01
        • 1970-01-01
        • 2020-03-24
        相关资源
        最近更新 更多