再帰を使用して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」ステートメントも逆にしましたが、それはアイテムの値に基づいて動作するメソッドを呼び出す前に型チェックを行いたいという単なる仮定でした... ActionDictionaryに対して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を反復処理します。
menu.Valuesを繰り返し処理し、繰り返しながら削除できます。値は並べ替えられた順序にはなりません(この場合は問題ありません)。 foreachを使用するのではなく、forループを使用してインデックスを調整する必要がある場合があります。反復中にコレクションを変更すると、列挙子は例外をスローします。
(開発マシンMonにいるときにコードを追加しようとします)
オプション2:カスタムイテレータを作成します。
WinformsのListBox SelectedItemsから返される一部のコレクションには、実際にはコレクションが含まれていません。これらは、基礎となるコレクションのラッパーを提供します。 WPFのCollectionViewSourceのようなもの。 ReadOnlyCollectionも同様の処理を行います。
「フラット化」できるクラスを作成します。ネストされた辞書を、単一のコレクションのように列挙できるものに変換します。コレクションからアイテムを削除するように見える削除関数を実装しますが、実際には現在の辞書から削除します。
My Opinionでは、 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();
}
高速で動作するはずです