75 votes

Meilleur moyen de supprimer plusieurs éléments correspondant à un prédicat dans un dictionnaire .NET ?

Je dois supprimer plusieurs éléments d'un dictionnaire. Une façon simple de le faire est la suivante :

  List<string> keystoremove= new List<string>();
  foreach (KeyValuePair<string,object> k in MyCollection)
     if (k.Value.Member==foo)
        keystoremove.Add(k.Key);
  foreach (string s in keystoremove)
        MyCollection.Remove(s);

La raison pour laquelle je ne peux pas supprimer directement les éléments dans le bloc foreach est que cela entraînerait une exception ("La collection a été modifiée...").

Je voudrais faire ce qui suit :

 MyCollection.RemoveAll(x =>x.Member==foo)

Mais la classe Dictionary<> n'expose pas une méthode RemoveAll(Predicate<> Match), comme le fait la classe List<>.

Quelle est la meilleure façon (à la fois en termes de performances et d'élégance) de le faire ?

105voto

JaredPar Points 333733

Voici une autre façon de procéder

foreach ( var s in MyCollection.Where(kv => kv.Value.Member == foo).ToList() ) {
  MyCollection.Remove(s.Key);
}

Le fait de placer le code directement dans une liste permet d'éviter le problème de "suppression pendant l'énumération". Le site .ToList() forcera l'énumération avant que le foreach ne commence vraiment.

1 votes

Bonne réponse, mais je ne pense pas que la fonction ToList() soit nécessaire. L'est-elle ?

0 votes

Bonne réponse, mais si je ne me trompe pas, s est une instance du type Value, donc la terminaison s.key ne compilera pas, n'est-ce pas ?

0 votes

Je ne pense pas que j'aime trop cette solution à cause du ".ToList()". Il est là pour une raison, mais cette raison n'est pas évidente tant que vous n'avez pas supprimé .ToList() et observé l'erreur vous-même. Je ne recommanderais pas ce code quand il existe des alternatives plus lisibles.

28voto

aku Points 54867

Vous pouvez créer un méthode d'extension :

public static class DictionaryExtensions
{
    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dict, 
        Func<TValue, bool> predicate)
    {
        var keys = dict.Keys.Where(k => predicate(dict[k])).ToList();
        foreach (var key in keys)
        {
            dict.Remove(key);
        }
    }
}

...

dictionary.RemoveAll(x => x.Member == foo);

0 votes

Ça marche très bien. Merci !

0 votes

Si vous voulez la valeur de l'élément predicate alors pourquoi tu énumères les clefs, et tu n'énumères pas la dict directement afin d'obtenir les paires clé-valeur ? En interrogeant le dictionnaire pour chaque clé, vous perdez en efficacité sans raison apparente.

15voto

David B Points 53123

Au lieu de supprimer, faites simplement l'inverse. Créez un nouveau dictionnaire à partir de l'ancien, contenant uniquement les éléments qui vous intéressent.

public Dictionary<T, U> NewDictionaryFiltered<T, U>
(
  Dictionary<T, U> source,
  Func<T, U, bool> filter
)
{
return source
  .Where(x => filter(x.Key, x.Value))
  .ToDictionary(x => x.Key, x => x.Value);
}

1 votes

D'où vient le filtre ? Pouvez-vous expliquer ce que c'est ?

1 votes

Vous aurez probablement besoin d'ajouter using System.Linq; pour que cela fonctionne.

11voto

Jerome Points 156

Version modifiée de la solution de la méthode d'extension d'Aku. La principale différence est qu'elle permet au prédicat d'utiliser la clé du dictionnaire. Une différence mineure est qu'elle étend IDictionary plutôt que Dictionary.

public static class DictionaryExtensions
{
    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dic,
        Func<TKey, TValue, bool> predicate)
    {
        var keys = dic.Keys.Where(k => predicate(k, dic[k])).ToList();
        foreach (var key in keys)
        {
            dic.Remove(key);
        }
    }
}

. . .

dictionary.RemoveAll((k,v) => v.Member == foo);

1 votes

J'ai annulé une modification apportée par la Communauté parce qu'elle n'incluait pas ToList() causant le problème de "suppression pendant l'énumération".

0 votes

Merci pour l'inspiration Jérôme. En plus de cette réponse et de celle de @Aku, j'ai créé des surcharges d'extension pour les éléments suivants Func<TKey, bool> et Func<KeyValuePair<TKey, TValue>> qui fonctionnent toutes les unes à côté des autres (sauf si TKey et TValue sont du même type lorsque le compilateur ne peut manifestement pas choisir entre le Func<TKey, bool> ou le Func<TValue, bool> ). Si une personne intéressée n'arrive pas à trouver comment les mettre en œuvre, envoyez-moi un message ici et je les afficherai :-)

1voto

wildriver Points 21

L'amélioration de Jérôme par rapport à la méthode d'Aku est bonne. Cependant, elle avait besoin d'une mise au point critique. J'ai soumis le code modifié. J'espère qu'il sera affiché bientôt, mais le voici pour référence :

.......

var keyValuePairs = dic.Where(predicate).ToList();
            foreach (var kvp in keyValuePairs)

.......

Le reste du code est correct.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X