views:

719

answers:

4

Anybody have a slicker way to do this? Seems like it should be easier than this, but I'm having a mental block. Basically I need to remove items from an dictionary and recurse into the values of the items that are also dictionaries.

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    var keysToRemove = new List<string>();
    foreach (var item in menu)
    {
        if (!GetIsPermitted(item.Value.Call))
        {
            keysToRemove.Add(item.Key);
        }
        else if (item.Value is ActionDictionary)
        {
            RemoveNotPermittedItems((ActionDictionary)item.Value);
            if (((ActionDictionary)item.Value).Count == 0)
            {
                keysToRemove.Add(item.Key);
            }
        }
    }
    foreach (var key in (from item in menu where keysToRemove.Contains(item.Key) select item.Key).ToArray())
    {
        menu.Remove(key);
    }
}

Action dictionary is like this:

public class ActionDictionary : Dictionary<string, IActionItem>, IActionItem
A: 

Your foreach loop is way more complicated than it needs to be. Just do:

foreach (var key in keysToRemove)
{
    menu.Remove(key);
}

I'm slightly surprised that Dictionary doesn't have a RemoveAll method but it doesn't look like it does...

Jon Skeet
This does not work. You cannot remove an item while iterating through a collection. You will get an exception that the collection has been modified.
Tim Scott
This isn't operating on the original array though, so that isn't an issue. He's talking about the second foreach, not the first.
Matthew Scharley
Gotcha, of course, Refer to my previous comment about "mental block". :) Still I'd like to think it can be done in one loop.
Tim Scott
Well, you could have a single "select" which only allowed through appropriate entries - then call ToDictionary at the end. It would be fairly ugly though.
Jon Skeet
A: 

Option 1: A Dictionary is still a Collection. Iterate over menu.Values.

You can iterate over menu.Values and remove them as you iterate. The values won't come in any sorted order (which should be fine for your case). You may need to use a for loop and adjust the index rather than using foreach - the enumerator will throw an exception if you modify the collection while iterating.

(I'll try to add the code when I'm on my dev machine Mon)

Option 2: Create a custom iterator.

Some collections returned from ListBox SelectedItems in Winforms don't really contain the collection, they provide a wrapper around the underlying collection. Kind of like CollectionViewSource in WPF. ReadOnlyCollection does something similar too.

Create a class that can "flatten" your nested dictionaries into something that can enumerate over them like they are a single collection. Implement a delete function that looks like it removes an item from the collection, but really removes from the current dictionary.

Geoff Cox
A: 
Lex Li
+1  A: 

You don't really need to collect the keys and iterate them again if you iterate the dictionary in reverse (from 'menu.Count - 1' to zero). Iterating in forward order will, of course, yield mutated collection exceptions if you start removing things.

I don't know what an ActionDictionary is, so I couldn't test your exact scenario, but here's an example using just Dictionary<string,object>.

    static int counter = 0;
    private static void RemoveNotPermittedItems(Dictionary<string, object> menu)
    {
        for (int c = menu.Count - 1; c >= 0; c--)
        {
            var key = menu.Keys.ElementAt(c);
            var value = menu[key];
            if (value is Dictionary<string, object>)
            {
                RemoveNotPermittedItems((Dictionary<string, object>)value);
                if (((Dictionary<string, object>)value).Count == 0)
                {
                    menu.Remove(key);
                }
            }
            else if (!GetIsPermitted(value))
            {
                menu.Remove(key);
            }
        }
    }

    // This just added to actually cause some elements to be removed...
    private static bool GetIsPermitted(object value)
    {
        if (counter++ % 2 == 0)
            return false;
        return true;
    }

I also reversed the 'if' statement, but that was just an assumption that you'd want to do type checking before calling a method to act on the item's value...it will work either way assuming 'GetIsPermitted' always returns TRUE for ActionDictionary.

Hope this helps.

Jared
This is correct. The exception is that I should not reverse the IF because it would make different behavior. If a parent item is not permitted I want to remove it even if it has permitted children.
Tim Scott