Удаление элементов из IDictionary с помощью рекурсии
-
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();
}
Должно работать быстро