Question

Quelqu'un a-t-il un moyen plus astucieux de faire cela? On dirait que ça devrait être plus facile que ça, mais j'ai un blocage mental. Fondamentalement, je dois supprimer des éléments d’un dictionnaire et rentrer dans les valeurs des éléments qui sont également des dictionnaires.

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

Le dictionnaire d'actions ressemble à ceci:

public class ActionDictionary : Dictionary<string, IActionItem>, IActionItem
Était-ce utile?

La solution

Vous n'avez pas vraiment besoin de collecter les clés et de les réitérer si vous parcourez le dictionnaire à l'envers (de 'menu.Count - 1' à zéro). En effectuant une itération dans l'ordre avant, vous obtiendrez bien sûr des exceptions de collecte mutées si vous commencez à supprimer des éléments.

Je ne sais pas ce qu'est un ActionDictionary, je ne pouvais donc pas tester votre scénario exact, mais voici un exemple utilisant uniquement 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;
    }

J'ai également inversé l'instruction 'if', mais il s'agissait simplement d'une hypothèse voulant que vous souhaitiez effectuer une vérification de type avant d'appeler une méthode pour agir sur la valeur de l'élément ... cela fonctionnerait dans les deux cas, en supposant que 'GetIsPermitted' soit toujours retourne TRUE pour ActionDictionary.

J'espère que cela vous aidera.

Autres conseils

Alors que foreach et GetEnumerator échouent, une boucle for fonctionne,

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

Mais ElementAt () est une fonctionnalité .NET 3.5.

Pour commencer, votre boucle foreach est bien plus compliquée qu'elle ne devrait l'être. Il suffit de faire:

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

Je suis un peu surpris que Dictionary n'ait pas de méthode RemoveAll mais il ne semble pas que ce soit le cas ...

Option 1: un dictionnaire est toujours une collection. Itérer sur menu.Values.

Vous pouvez parcourir le menu menu.Values ??et les supprimer au fur et à mesure. Les valeurs ne seront pas classées (ce qui devrait convenir à votre cas). Vous devrez peut-être utiliser une boucle for et ajuster l'index plutôt que foreach. L'énumérateur lèvera une exception si vous modifiez la collection lors de l'itération.

(Je vais essayer d'ajouter le code quand je suis sur ma machine de développement Mon)

Option 2: créer un itérateur personnalisé.

Certaines collections renvoyées par ListBox SelectedItems dans Winforms ne contiennent pas vraiment la collection, elles fournissent un wrapper autour de la collection sous-jacente. Un peu comme CollectionViewSource dans WPF. ReadOnlyCollection fait quelque chose de similaire aussi.

Créez une classe qui peut "aplatir "" vos dictionnaires imbriqués dans quelque chose qui peut énumérer sur eux comme s'ils étaient une collection unique. Implémentez une fonction de suppression qui semble supprimer un élément de la collection, mais réellement du dictionnaire actuel.

Dans Mon avis, vous pouvez définir votre propre classe générique dérivée de KeyValuePair < ... > TKey et TValue seront Liste < T > et vous-même. pouvez utiliser le RemoveAll ou le RemoveRange de la Liste < T > dans un nouveau RemoveRange () ou La méthode RemoveAll () de votre classe dérivée pour supprimer les éléments souhaités.

Je sais que vous avez probablement déjà trouvé la bonne solution, mais pour la simple raison que vous modifiez vos signatures de méthode (vous savez que ce n'est peut-être pas approprié dans votre scénario):

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

Et je peux voir plusieurs manières d’utiliser votre dictionnaire avec des éléments filtrés sans modification ni matérialisation de nouveaux dictionnaires.

Ce n’est pas beaucoup moins compliqué, mais certains changements idiomatiques le rendent un peu plus court et plus agréable pour les yeux:

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

Modifiez le type de keysToRemove en HashSet < string > et vous obtiendrez une méthode O (1) Contains . Avec List < string > , c’est O (n), ce qui est plus lent que vous pouvez le deviner.

non testé jusqu'à ce que je sois demain sur ma machine 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);
        }
    }
}

Je pense

public class ActionSet : HashSet<IActionItem>, IActionItem

Et

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

Devrait travailler vite

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top