Удаление элементов из IDictionary с помощью рекурсии

StackOverflow https://stackoverflow.com/questions/235446

  •  04-07-2019
  •  | 
  •  

Вопрос

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

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
Это было полезно?

Решение

На самом деле вам не нужно собирать ключи и повторять их снова, если вы перебираете словарь в обратном порядке (от «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.

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

Другие советы

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

<код>

<код>
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.

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

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

Я немного удивлен, что у Dictionary нет метода RemoveAll , но он выглядит не так, как ...

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

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

(Я постараюсь добавить код, когда буду на своей машине разработчика в понедельник)

Вариант 2:Создайте собственный итератор.

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

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

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

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

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

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

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

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

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

не проверял, пока я не буду завтра на моей машине 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);
        }
    }
}

Я думаю

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top