views:

218

answers:

4

Hello!

Assume that we have class

public class RMenuItem
{
    public List<RMenuItem> ChildrenItems { get; }
    public decimal OperationID { get; }
    public string Name { get; }
}

as you can see - each menuitem could have children items - as usual in menu. My task is to iterate through each items of this list and apply some action to it. Classical decision is to write recursive iteration. But I'm interesting if LINQ could make my task easier? For example, I suppose that we can write query that can get flat list of objects, which i can iterate simply with foreach. But my attempts in this way weren't successful yet. So any help appreciated!

+4  A: 

It's possible:

    public void PrintAllNames(RMenuItem rootItem)
    {
        Action<RMenuItem> print = null;
        print = m =>
            {
                Console.WriteLine(m.Name);
                m.ChildrenItems.ForEach(print);
            };
        print(rootItem);
    }

Notice how it's necessary to declare print so that print can use itself. This is directly comparable to a recursive method, which I'd rather use:

    public void PrintAllNames(RMenuItem rootItem)
    {
        Console.WriteLine(rootItem.Name);
        rootItem.ChildrenItems.ForEach(PrintAllNames);
    }

(although for a more complex situation, maybe the functional solution would make the most sense)

Rob Fonseca-Ensor
Thanks all gyus, but it seems to me that classical recursion is the only acceptable decision because of its simplicity. So rather it's not the decision using LINQ, I'll choose it as answer
Andrey Khataev
+1  A: 

Indeed you can do that using LINQ, SelectMany flats out the list, just some example

menuItemsList.SelectMany(x => x.ChildrenItems).Where(c => c.someChildProperty);

Thanks

Edit:

In response to the comments, I was just giving an example of SelectMany previously. Thanks for pointing out.

menuItemsList.SelectMany(x => x.ChildrenItems.Select(p => p)).Where(c => c.someChildProperty);

OR something like this

menuItemsList.SelectMany(x => x.ChildrenItems).Select(p => p).Where(c => c.someChildProperty);

Edit2

Ahh .. now I understood what you want ..

We can just slightly modify my above query to do what you want

menuItemsList
.SelectMany(x => { //do something with x like printing it  
                    x.ChildrenItems 
                 })
.Select(p => { // do something with p like printing it 
                  p 
             });

Basically you can do what you want the element inside the {}

Thanks

Mahesh Velaga
This won't work. I also thought it would, but you only get the first level Menus...
bruno conde
This only gets the first level of child items.
Rob Fonseca-Ensor
Thanks for pointing out guys :)
Mahesh Velaga
I think that what andrey wants is an algorithm that will visit all of children, grandchildren etc. The where clause doesn't help with this
Rob Fonseca-Ensor
@Rob He actually didn't mention clearly (atleast not clear to me) what he wants exacgtly, so I am just giving an example of how he can make use of SelectMany in Linq. Thanks :)
Mahesh Velaga
I've mentioned that I need to iterate through EACH item. This means i need visit every children till last level of hierarchy OR concerning to linq query - get flat list of all items.
Andrey Khataev
You are getting all the children in my answer :), I just added a where class so as to demonstrate if you wanted to do any querying on the children .. Thanks
Mahesh Velaga
No. Your solution doesn't work. menuItemsList is a ObservableCollection<RMyMenuItem>, so in SelectMany x parameter is a RMyMenuItem, so it doesn't have Select operator. More over SelectMany applies some function to each element of top level and not further. I think here we can't work it out without recursion
Andrey Khataev
@Andrey sorry after edit I accidentally deleted the .ChilderenItems in the query. Is still something wrong ?
Mahesh Velaga
They both select only child items of second depth level.
Andrey Khataev
Yes .. isn't that what you wanted ? the flat list of grand children ?
Mahesh Velaga
I think your present solution will now get all till 3rd level. But the number of levels are not known beforehand. It seems like that there is no solution in LINQ without the same recursion. I just thought that there is native support for hierarchical queries or something similar. Just like in Oracle. But anyway thanks!
Andrey Khataev
A: 

I suggest 2 ways of achieving this. You can opt with an utility method to get all the items or you can implement the Visitor Pattern, though it implies changing the RMenuItem class.

Utility method:

    static IEnumerable<RMenuItem> GetAllMenuItems(IList<RMenuItem> items)
    {
        if (items == null)
            throw new ArgumentNullException("items");

        Queue<RMenuItem> queue = new Queue<RMenuItem>(items);

        while (queue.Count > 0)
        {
            var item = queue.Dequeue();
            if (item.ChildrenItems != null)
            {
                foreach (var child in item.ChildrenItems)
                {
                    queue.Enqueue(child);
                }
            }
            yield return item;
        }
    }

I prefer an imperative way to a recursive because we can use iterator blocks.

Visitor Pattern:

    public interface IRMenuItemVisitor
    {
        void Visit(RMenuItem item);
    }

    public class PrintRMenuItemVisitor : IRMenuItemVisitor
    {
        public void Visit(RMenuItem item)
        {
            Console.WriteLine(item);
        }
    }

    public interface IRMenuItem
    {
        void Accept(IRMenuItemVisitor visitor);
    }

    public class RMenuItem : IRMenuItem
    {
        // ...

        public void Accept(IRMenuItemVisitor visitor)
        {
            visitor.Visit(this);
            if (ChildrenItems != null)
            {
                foreach (var item in ChildrenItems)
                {
                    item.Accept(visitor);
                }
            }
        }
    }

Usage:

        RMenuItem m1 = new RMenuItem
        {
            Name = "M1",
            ChildrenItems = new List<RMenuItem> { 
                new RMenuItem { Name = "M11" }, 
                new RMenuItem { 
                    Name = "M12", 
                    ChildrenItems = new List<RMenuItem> {
                        new RMenuItem { Name = "M121" },
                        new RMenuItem { Name = "M122" }
                    }
                } 
            }
        };

        RMenuItem m2 = new RMenuItem
        {
            Name = "M2",
            ChildrenItems = new List<RMenuItem> { 
                new RMenuItem { Name = "M21" }, 
                new RMenuItem { Name = "M22" }, 
                new RMenuItem { Name = "M23" } 
            }
        };

        IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
        foreach (var menu in GetAllMenuItems(menus))
        {
            Console.WriteLine(menu);
        }

        // or

        IList<RMenuItem> menus = new List<RMenuItem> { m1, m2 };
        foreach (var menu in menus)
        {
            menu.Accept(new PrintRMenuItemVisitor());
        }
bruno conde
Downvoter, care to explain?
bruno conde
+1 for the comment, please leave a comment when down voting so that author knows whats wrong with the post and it helps. Thanks
Mahesh Velaga
that wasn't me to downvote
Andrey Khataev
I'll try this solution but it starts to seem to me that recursive solution is much more simple and intuitively clear))
Andrey Khataev
@Andrey Khataev, I updated my answer.
bruno conde
+1  A: 

You could difine a Flatten method in your class (or as an extension if you prefer) like this

public IEnumerable<RMenuItem> Flatten()
{
    foreach (var item in ChildrenItems)
    {
        yield return item;
    }
    return ChildrenItems.SelectMany(item => item.Flatten());
}

then doing somthing with each elements will be as simple as

RMenuItem rootItem ;

    // do somthing with the root item
    foreach (var item  in rootItem.Flatten())
    {
        // do somthing
    }
Johnny Blaze