Linq扩展方法,如何在集合中递归查找子级

发布于 2024-09-16 18:09:04 字数 272 浏览 5 评论 0原文

我已经熟悉 Linq 但对扩展方法知之甚少,我希望有人可以帮助我。

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

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

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

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

I'm already familiar with Linq but have little understanding of extension methods I'm hoping someone can help me out.

So I have this hierarchical collection pseudo code ie:

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

And I have a list of products List products.

Is there any way I can look for product in this collection by the id with a extension method ? In other words I need one item somewhere within the hierarchy.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

浮光之海 2024-09-23 18:09:04

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

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

Here is a generic solution that will short-circuit traversal of the hierarchy once a match is found.

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

To use it in your case:

var matchingProduct = products.FirstOrDefaultFromMany(p => p.children, p => p.Id == 27);
望喜 2024-09-23 18:09:04

您可以使用此扩展方法展平树结构:

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

You can flatten your tree structure using this extension method:

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

Usage:

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

Note that this is probably not very fast. If you repeatedly need to find a product by id, create a dictionary:

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

var product42 = dict[42];
雨落星ぅ辰 2024-09-23 18:09:04

我只是重构 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);

I'm just refactoring dtb's solution to make it more generic. Try this Extension method:

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

And you can use it like this:

productList.Flatten(x => x.Children).Where(x => x.ID == id);
ヤ经典坏疍 2024-09-23 18:09:04
static IEnumerable<Product> FindProductById(this IEnumerable<Product> source, int id) 
{
    return source.FirstOrDefault(product => product.Id = id) ?? source.SelectMany(product => product.Children).FindProductById(id);
}
static IEnumerable<Product> FindProductById(this IEnumerable<Product> source, int id) 
{
    return source.FirstOrDefault(product => product.Id = id) ?? source.SelectMany(product => product.Children).FindProductById(id);
}
恬淡成诗 2024-09-23 18:09:04

另一种解决方案是使用产量来优化所需的枚举。

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

An alternative solution using the yield to optimize the enumerations needed.

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

Then you can find a match by calling something like FirstOrDefault:

    var match = People.SelectManyRecursive(c => c.Children)
                      .FirstOrDefault(x => x.Id == 5);
清晰传感 2024-09-23 18:09:04

如果您想要“子迭代”并在产品列表中查找子项:

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

If you want to "sub-iterate" and find a child in a list of Products:

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

You can use the existing SelectMany extension method. SelectMany can be used to "flatten" a two-level hierarchy.

Here's a great explanation of SelectMany: http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

Your syntax would like like this:

List<Product> p = GetProducts(); //Get a list of products
var child = from c in p.SelectMany(p => p.Children).Where(c => c.Id == yourID);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文