使用递归从 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”语句,但这只是一个假设,即您希望在调用对项目的值进行操作的方法之前进行类型检查...假设“GetIsPermission”始终返回 TRUE,那么无论哪种方式都可以工作行动词典。
希望这可以帮助。
其他提示
当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 - 如果在迭代时修改集合,枚举器将抛出异常。
(当我在我的开发机器Mon上时,我会尝试添加代码)
选项2:创建自定义迭代器。
从Winforms中的ListBox SelectedItems返回的一些集合实际上并不包含集合,它们提供了底层集合的包装。有点像WPF中的CollectionViewSource。 ReadOnlyCollection也做了类似的事情。
创建一个可以“展平”的类你的嵌套词典可以枚举它们,就像它们是一个集合一样。实现一个删除函数,它看起来像是从集合中删除了一个项目,但实际上是从当前字典中删除的。
在我的意见中,你可以定义你自己的泛型类派生自 KeyValuePair&lt; ...&gt;
TKey和TValue将是 List&lt; T&gt;
而你可以在新的 RemoveRange()
或 List&lt; T&gt;
的 RemoveAll
或 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&lt; string&gt;
,您将获得O(1) Contains
方法。使用 List&lt; string&gt;
,它是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();
}
应该快速工作