Filter a hierarchical list using linq

c# expression-trees linq

Question

Here is my model for Product

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

I need to filter the above list which contain the ProductNumber less than 5, ie. the output is expected to be a list of Product which has product numbers less than 5.

is there any extensions available? Please help.

Here my expected result

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

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

Accepted Answer

It's fairly easy to knock together a "flatten-this-tree" LINQ-like function

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

which you can then use in regular LINQ e.g.

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

EDIT: From your edit I can see that this isn't quite what you wanted. (You don't want a list of products, you want a tree of products...) I'm going to leave this here as I quite like it as a solution to what I thought your problem was, but I'll have a think about your new problem too...

EDIT: If you don't mind modifying your original objects, you can use this as follows:

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

Popular Answer

You need something like this:

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

which you'll call like this:

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


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why