Удаление элементов из IDictionary с помощью рекурсии - PullRequest
3 голосов
/ 25 октября 2008

У кого-нибудь есть более приятный способ сделать это? Похоже, это должно быть проще, чем это, но у меня психическое расстройство. По сути, мне нужно удалить элементы из словаря и ввести значения элементов, которые также являются словарями.

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

Словарь действий выглядит так:

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

Ответы [ 10 ]

3 голосов
/ 25 октября 2008

Вам на самом деле не нужно собирать ключи и повторять их снова, если вы перебираете словарь в обратном порядке (от «menu.Count - 1» до нуля). Итерации в прямом порядке, конечно, приведут к мутировавшим исключениям коллекции, если вы начнете удалять вещи.

Я не знаю, что такое ActionDictionary, поэтому я не смог проверить ваш точный сценарий, но вот пример, использующий просто 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;
    }

Я также отменил оператор if, но это было лишь предположение, что вы хотите выполнить проверку типов перед вызовом метода для воздействия на значение элемента ... он будет работать в любом случае, предполагая, что GetIsPermitted всегда возвращает TRUE для ActionDictionary.

Надеюсь, это поможет.

2 голосов
/ 25 октября 2008

Пока foreach и GetEnumerator не работают, цикл for работает,

</p> <pre><code>var table = new Dictionary<string, int>() {{"first", 1}, {"second", 2}}; for (int i = 0; i < table.Keys.Count; i++)//string key in table.Keys) { string key = table.Keys.ElementAt(i); if (key.StartsWith("f")) { table.Remove(key); } }

Но ElementAt () - это функция .NET 3.5.

2 голосов
/ 25 октября 2008

Начнем с того, что ваш цикл foreach намного сложнее, чем нужно. Просто сделай:

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

Я немного удивлен, что Dictionary не имеет RemoveAll метода, но, похоже, не имеет ...

1 голос
/ 18 мая 2012

Я думаю

public class ActionSet : HashSet<IActionItem>, IActionItem

И

bool Clean(ActionSet nodes)
    {
        if (nodes != null)
        {
            var removed = nodes.Where(n => this.IsNullOrNotPermitted(n) || !this.IsNotSetOrNotEmpty(n) || !this.Clean(n as ActionSet));

            removed.ToList().ForEach(n => nodes.Remove(n));

            return nodes.Any();
        }

        return true;
    }

    bool IsNullOrNotPermitted(IActionItem node)
    {
        return node == null || *YourTest*(node.Call);
    }

    bool IsNotSetOrNotEmpty(IActionItem node)
    {
        var hset = node as ActionSet;
        return hset == null || hset.Any();
    }

Должно работать быстро

1 голос
/ 18 мая 2012

не проверял, пока я не буду завтра на моей машине VS: o

private void RemoveNotPermittedItems(ActionDictionary menu)
{
    foreach(var _checked in (from m in menu
                             select new
                             {
                                 gip = !GetIsPermitted(m.Value.Call),
                                 recur = m.Value is ActionDictionary,
                                 item = m
                             }).ToArray())
    {
        ActionDictionary tmp = _checked.item.Value as ActionDictionary;
        if (_checked.recur)
        {
            RemoveNotPermittedItems(tmp);
        }
        if (_checked.gip || (tmp != null && tmp.Count == 0) {
            menu.Remove(_checked.item.Key);
        }
    }
}
1 голос
/ 17 мая 2012

Измените тип keysToRemove на HashSet<string>, и вы получите метод O (1) Contains. С List<string> это O (n), что медленнее, чем вы можете догадаться.

1 голос
/ 17 мая 2012

Это не намного менее сложно, но некоторые идиоматические изменения делают его немного короче и легче для глаз:

    private static void RemoveNotPermittedItems(IDictionary<string, IActionItem> menu)
    {
        var keysToRemove = new List<string>();

        foreach (var item in menu)
        {
            if (GetIsPermitted(item.Value.Call))
            {
                var value = item.Value as ActionDictionary;

                if (value != null)
                {
                    RemoveNotPermittedItems(value);
                    if (!value.Any())
                    {
                        keysToRemove.Add(item.Key);
                    }
                }
            }
            else
            {
                keysToRemove.Add(item.Key);
            }
        }

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

    private static bool GetIsPermitted(object call)
    {
        return ...;
    }
1 голос
/ 16 мая 2012

Я знаю, что вы, вероятно, уже нашли хорошее решение, но только по причине «гладкости», если бы вы могли изменить сигнатуры вашего метода на (я знаю, что это может не подходить в вашем сценарии):

private ActionDictionary RemoveNotPermittedItems(ActionDictionary menu)
{
 return new ActionDictionary(from item in menu where GetIsPermitted(item.Value.Call) select item)
.ToDictionary(d=>d.Key, d=>d.Value is ActionDictionary?RemoveNotPermittedItems(d.Value as ActionDictionary) : d.Value));
}

И я вижу несколько способов использования своего словаря с отфильтрованными элементами без изменения и материализации новых словарей.

1 голос
/ 12 мая 2012

В My Opinion вы можете определить свой собственный родовой класс, полученный из KeyValuePair<...>, и TKey и TValue будут List<T>, и вы можете использовать RemoveAll или RemoveRange List<T> в RemoveRange() или RemoveAll() метод в вашем производном классе для удаления элементов, которые вы хотите.

1 голос
/ 25 октября 2008

Вариант 1: словарь по-прежнему является коллекцией. Перебор по меню. Значения.

Вы можете перебирать меню. Значения и удалять их при переборе. Значения не появятся в каком-либо отсортированном порядке (что должно быть хорошо для вашего случая). Возможно, вам придется использовать цикл for и настроить индекс, а не использовать foreach - перечислитель выдаст исключение, если вы измените коллекцию во время итерации.

(я попытаюсь добавить код, когда я нахожусь на моей машине разработчика Mon)

Вариант 2. Создание пользовательского итератора.

Некоторые коллекции, возвращаемые из ListBox SelectedItems в Winforms, на самом деле не содержат коллекцию, они предоставляют обертку вокруг базовой коллекции. Вроде как CollectionViewSource в WPF. ReadOnlyCollection тоже делает нечто подобное.

Создайте класс, который может "сгладить" ваши вложенные словари во что-то, что может перечислять их, как если бы они были одной коллекцией. Реализуйте функцию удаления, которая выглядит так, как будто она удаляет элемент из коллекции, но действительно удаляет из текущего словаря.

...