使用 LINQ 过滤层次列表

4

这是我的产品模型

public class Product
{
    public string Name{ get; set; }

    public int ProductNumber{ get; set; }

    public List<Product> ProductList { get; set; }
}

//// below is the structure of the list
IList<Product> rootList = new List<Product>
            {
                new Product 
                { 
                    ProductNumber = 1, Name = "A", 
                    ProductList = new List<Product> { new Product { ProductNumber = 2, Name = "A1", 
                        ProductList = new List<Product> { new Product { ProductNumber = 3, Name = "A2", ProductList = new List<Product>()} }}  
                    }
                },

                new Product 
                { 
                    ProductNumber = 4, Name = "B", 
                    ProductList = new List<Product> { new Product { ProductNumber = 5, Name = "B1", 
                        ProductList = new List<Product> { new Product { ProductNumber = 6, Name = "B2", ProductList = new List<Product>()} }}  
                    }
                },

                 new Product 
                { 
                    ProductNumber = 7, Name = "C", 
                    ProductList = new List<Product> { new Product { ProductNumber = 8, Name = "C1", 
                        ProductList = new List<Product> { new Product { ProductNumber = 9, Name = "C2", ProductList = new List<Product>()} }}  
                    }
                }
            };

我需要过滤含有产品编号小于5的上述列表,即期望输出是一个具有产品编号小于5的产品列表。

是否有可用的扩展程序?请帮忙。

这是我的期望结果。

            Product 
            { 
                ProductNumber : 1, 
                Name : "A", 
                ProductList : { { 
                          ProductNumber : 2, 
                          Name : "A1", 
                          ProductList :{ { 
                                  ProductNumber = 3, 
                                  Name : "A2", 
                                  ProductList : null} }}  
                }
            },

            Product 
            { 
                ProductNumber : 4, 
                Name : "B"
                ProductList : null
            } 

2
你是在谈论筛选那些内部产品列表中有数字小于五的产品吗?或者所有内部列表都需要被过滤以删除产品?我有点困惑...也许如果你说明你想要返回什么,这可能会更清楚一些...我假设它不仅仅是你的根列表中的产品具有产品编号<5,因为那只是简单的linq... - Chris
我已经更新了我的预期结果结构。你能看一下吗? - Bibin
我已经编写了一些代码,让您可以获得想要的结果,前提是您愿意修改原始树。如果不想修改原始树,您需要考虑对其进行深度克隆,然后在克隆上运行代码。 - Rawling
-1,你要求一个特定的功能和输出,但是却接受了完全不同的答案,你至少应该考虑将你的问题调整为已接受的答案。 - Wasp
2个回答

4

很容易编写一个类似LINQ的“压平这棵树”的函数。

public static IEnumerable<T> Flatten<T>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<T>> childSelector)
{
    HashSet<T> added = new HashSet<T>();
    Queue<T> queue = new Queue<T>();
    foreach(T t in source)
        if (added.Add(t))
            queue.Enqueue(t);
    while (queue.Count > 0)
    {
        T current = queue.Dequeue();
        yield return current;
        if (current != null)
        {
            IEnumerable<T> children = childSelector(current);
            if (children != null)
                foreach(T t in childSelector(current))
                    if (added.Add(t))
                        queue.Enqueue(t);
        }
    }
}

您可以将其用于常规的LINQ,例如:

var lessThanFive = rootList
    .Flatten(p => p.ProductList)
    .Where(p => p.ProductNumber < 5)
    .ToList();

编辑: 从您的编辑中我可以看到这不是您想要的。 (您不想要产品列表,您想要产品树...) 我将把这个留在这里,因为我认为这是一个解决我认为您的问题的好方法,但我也会考虑一下您的新问题...

编辑: 如果您不介意修改原始对象,您可以按如下方式使用它:

rootList = rootList.Where(p => p.ProductNumber < 5).ToList();
foreach (var pr in rootList.Flatten(p => p.ProductList))
    pr.ProductList = pr.ProductList.Where(p => p.ProductNumber < 5).ToList();

我已经为特定情况做过这种事情很多次了,我很惊讶自己之前为什么没有组合出这样的通用方法。 - Rawling

0
你需要类似这样的东西:
public static class EnumerableExtensions
{
    public static IEnumerable<TR> Recur<T, TR>(
        this IEnumerable<T> source, 
        Func<T, bool> filter, 
        Func<T, IEnumerable<T>> recursor, 
        Func<T, IEnumerable<T>, TR> resultor)
    {
        foreach(var t in source)
            if (filter(t))
                yield return resultor(t, recursor(t));
    }
}

你可以这样调用:

var q = rootList.Recur(
    p => p.ProductNumber < 5,
    p => p.ProductList,
    (p, cs) => new 
    {
        p.ProductNumber, 
        p.Name,
        ProductList = cs 
    });

感谢您的努力,但是这个解决方案对于更高的页面编号(比如8)并不起作用。 - Bibin
请您详细说明一下?根据您的要求,该方法接受一个谓词并产生满足它的元素。对于不满足条件的元素,如您的示例中所示,将被排除,编号为8。 - Wasp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接