web-dev-qa-db-fra.com

List <object> .RemoveAll - Comment créer un prédicat approprié

C'est un peu une question noob - je suis encore assez nouveau pour C # et les génériques et complètement nouveau pour les prédicats, les délégués et les expressions lambda ...

J'ai une classe "Demandes" qui contient une liste générique d'une autre classe appelée "Véhicules". Je construis le code pour ajouter/modifier/supprimer des véhicules de l'enquête parent. Et en ce moment, je regarde spécifiquement les suppressions.

D'après ce que j'ai lu jusqu'à présent, il semble que je puisse utiliser Vehicles.RemoveAll () pour supprimer un élément avec un VehicleID particulier ou tous les éléments avec un EnquiryID particulier. Mon problème est de comprendre comment nourrir .SupprimerTout le bon prédicat - les exemples que j'ai vus sont trop simplistes (ou peut-être je suis trop simpliste étant donné mon manque de connaissance des prédicats, des délégués et des expressions lambda).

Donc, si j'avais un List<Of Vehicle> Vehicles Où chaque véhicule avait un EnquiryID, comment utiliserais-je Vehicles.RemoveAll() pour supprimer tous les véhicules pour un EnquiryID donné?

Je comprends qu'il existe plusieurs approches à cela, donc je serais ravi d'entendre les différences entre les approches - autant que j'ai besoin de faire fonctionner quelque chose, c'est aussi un exercice d'apprentissage.

Comme question supplémentaire, une liste générique est-elle le meilleur référentiel pour ces objets? Ma première inclination était vers une Collection, mais il semble que je sois dépassé. Certes, les génériques semblent être préférés, mais je suis curieux de voir d'autres alternatives.

40
CJM

Les méthodes RemoveAll() acceptent un Predicate<T> délégué (jusqu'ici rien de nouveau). Un prédicat pointe vers une méthode qui renvoie simplement vrai ou faux. Bien entendu, RemoveAll supprimera de la collection toutes les instances T qui renvoient True avec le prédicat appliqué.

C # 3.0 permet au développeur d'utiliser plusieurs méthodes pour passer un prédicat à la méthode RemoveAll (et pas seulement celle-ci…). Vous pouvez utiliser:

Expressions lambda

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);

Méthodes anonymes

vehicles.RemoveAll(delegate(Vehicle v) {
  return v.EnquiryID == 1;
});

Méthodes normales

vehicles.RemoveAll(VehicleCustomPredicate);
private static bool
VehicleCustomPredicate (Vehicle v) {
    return v.EnquiryID == 1; 
}
84
AngeloBad

Un prédicat dans T est un délégué qui prend un T et renvoie un booléen. List <T> .RemoveAll supprimera tous les éléments d'une liste où l'appel du prédicat renvoie true. Le moyen le plus simple de fournir un prédicat simple est généralement un expression lambda , mais vous pouvez également utiliser méthodes anonymes ou des méthodes réelles.

{
    List<Vehicle> vehicles;
    // Using a lambda
    vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);
    // Using an equivalent anonymous method
    vehicles.RemoveAll(delegate(Vehicle vehicle)
    {
        return vehicle.EnquiryID == 123;
    });
    // Using an equivalent actual method
    vehicles.RemoveAll(VehiclePredicate);
}

private static bool VehiclePredicate(Vehicle vehicle)
{
    return vehicle.EnquiryID == 123;
}
15
Quartermeister

Cela devrait fonctionner (où enquiryId est l'ID auquel vous devez faire correspondre):

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == enquiryId);

Cela fait passer chaque véhicule de la liste dans le prédicat lambda, en évaluant le prédicat. Si le prédicat renvoie vrai (c'est-à-dire. vehicle.EnquiryID == enquiryId), le véhicule actuel sera supprimé de la liste.

Si vous connaissez les types d'objets dans vos collections, l'utilisation des collections génériques est une meilleure approche. Il évite la conversion lors de la récupération d'objets dans les collections, mais peut également éviter la boxe si les éléments de la collection sont des types de valeur (ce qui peut entraîner des problèmes de performances).

6
adrianbanks

Je voulais aborder quelque chose qu'aucune des réponses n'a jusqu'à présent:

D'après ce que j'ai lu jusqu'à présent, il semble que je puisse utiliser Vehicles.RemoveAll () pour supprimer un élément avec un VehicleID particulier. En tant que question supplémentaire [sic], une liste générique est-elle le meilleur référentiel pour ces objets?

En supposant que VehicleID est unique comme son nom l'indique, une liste est un moyen terriblement inefficace de les stocker lorsque vous obtenez beaucoup de véhicules, car la suppression (et d'autres méthodes comme Find) est toujours O ( n). Jetez un oeil à un HashSet<Vehicle> à la place, il a O(1) suppression (et d'autres méthodes) en utilisant:

int GetHashCode(Vehicle vehicle){return vehicle.VehicleID;}
int Equals(Vehicle v1, Vehicle v2){return v1.VehicleID == v2.VehicleID;}

La suppression de tous les véhicules avec un EnquiryID spécifique nécessite toujours une itération sur tous les éléments de cette manière, vous pouvez donc envisager un GetHashCode qui renvoie le EnquiryID à la place, en fonction de l'opération que vous effectuez plus souvent. Cela a cependant l'inconvénient de nombreuses collisions si de nombreux véhicules partagent le même EnquiryID.

Dans ce cas, une meilleure alternative est de faire un Dictionary<int, List<Vehicle>> qui associe les EnquiryID aux véhicules et les garde à jour lors de l'ajout/la suppression de véhicules. La suppression de ces véhicules d'un HashSet est alors une opération O(m), où m est le nombre de véhicules avec un EnquiryID spécifique.

1
Wolfzoon

Un peu hors sujet mais dis que je veux supprimer tous les 2 d'une liste. Voici une façon très élégante de le faire.

void RemoveAll<T>(T item,List<T> list)
{
    while(list.Contains(item)) list.Remove(item);
}

Avec prédicat:

void RemoveAll<T>(Func<T,bool> predicate,List<T> list)
{
    while(list.Any(predicate)) list.Remove(list.First(predicate));
}

+1 uniquement pour vous encourager à laisser votre réponse ici à des fins d'apprentissage. Vous avez également raison de dire que c'est hors sujet, mais je ne vous en dirai pas trop car il est important de laisser vos exemples ici, encore une fois, strictement à des fins d'apprentissage. Je poste cette réponse sous forme de modification, car la publier sous forme de série de commentaires serait indiscipliné.

Bien que vos exemples soient courts et compacts, aucun n'est élégant en termes d'efficacité; le premier est mauvais à O (n2), le second, absolument abyssal en O (n3). Efficacité algorithmique de O (n2) est mauvais et doit être évité autant que possible, en particulier dans le code à usage général; efficacité de O (n3) est horrible et doit être évité dans tous les cas, sauf lorsque vous savez que n sera toujours très petit. Certains pourraient déployer leur "optimisation prématurée est la racine de tous les mauvais axes de bataille", mais ils le font naïvement parce qu'ils ne comprennent pas vraiment les conséquences de la croissance quadratique car ils n'ont jamais codé d'algorithmes qui doivent traiter de grands ensembles de données. En conséquence, leurs algorithmes de gestion de petits ensembles de données fonctionnent généralement plus lentement qu'ils ne le pouvaient, et ils n'ont aucune idée qu'ils pourraient fonctionner plus rapidement. La différence entre un algorithme efficace et un algorithme inefficace est souvent subtile, mais la différence de performances peut être dramatique. La clé pour comprendre les performances de votre algorithme est de comprendre les caractéristiques de performances des primitives que vous choisissez d'utiliser.

Dans votre premier exemple, list.Contains() et Remove() sont tous les deux O (n), donc une boucle while() avec une dans le prédicat et l'autre dans le corps est O (n2); bien, techniquement O (m * n), mais il se rapproche de O (n2) lorsque le nombre d'éléments supprimés (m) approche de la longueur de la liste (n).

Votre deuxième exemple est encore pire: O (n3), car à chaque fois que vous appelez Remove(), vous appelez également First(predicate), qui est également O (n). Pensez-y: Any(predicate)boucle sur la liste recherche de tout élément pour lequel predicate() retourne vrai. Une fois qu'il trouve le premier élément de ce type, il renvoie vrai. Dans le corps de la boucle while(), vous appelez ensuite list.First(predicate) qui boucle sur la liste une deuxième fois en recherchant le même élément qui avait déjà été trouvé par list.Any(predicate). Une fois que First() l'a trouvé, il retourne cet élément qui est passé à list.Remove(), qui boucle une troisième fois sur la liste pour retrouver encore une fois le même élément précédemment trouvé par Any() et First(), afin de le supprimer définitivement. Une fois supprimé, le tout le processus recommence au début avec une liste légèrement plus courte, faisant tout le bouclage encore et encore et toujours en commençant au début à chaque fois jusqu'à ce que finalement non il reste plus d'éléments correspondant au prédicat. Ainsi, les performances de votre deuxième exemple sont O (m * m * n) ou O (n3) à mesure que m approche n.

Votre meilleur pari pour supprimer tous les éléments d'une liste qui correspondent à un prédicat est d'utiliser la propre méthode List<T>.RemoveAll(predicate) de la liste générique, qui est O(n) tant que votre prédicat est O (1). Une technique de boucle for() qui passe sur la liste une seule fois, appelant list.RemoveAt() pour chaque élément à supprimer, peut sembler être O(n) car il semble passer sur la boucle une seule fois. Une telle solution est plus efficace que votre premier exemple, mais uniquement par un facteur constant, qui en termes d'efficacité algorithmique est négligeable. Même une implémentation de boucle for() est O (m * n) puisque chaque appel à Remove() est O (n). Puisque la fonction for() la boucle elle-même est O (n), et elle appelle Remove() m fois, la croissance de la boucle for() est O (n2) à mesure que m approche n.

1
Raz Megrelidze