Utilisation de LINQ pour supprimer des éléments d'une liste <T>


655

Disons que j'ai une requête LINQ telle que:

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

Étant donné que authorsListc'est de type List<Author>, comment puis-je supprimer leAuthor éléments authorsListqui sont retournés par la requête dans authors?

Ou, en d'autres termes, comment puis-je supprimer tous les Bob égaux du prénom de authorsList ?

Remarque: Il s'agit d'un exemple simplifié aux fins de la question.

Réponses:


1139

Eh bien, il serait plus facile de les exclure en premier lieu:

authorsList = authorsList.Where(x => x.FirstName != "Bob").ToList();

Cependant, cela ne ferait que modifier la valeur de authorsListau lieu de supprimer les auteurs de la collection précédente. Alternativement, vous pouvez utiliser RemoveAll:

authorsList.RemoveAll(x => x.FirstName == "Bob");

Si vous avez vraiment besoin de le faire sur la base d'une autre collection, j'utiliserais un HashSet, RemoveAll et Contains:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));

14
Quelle est la raison d'utiliser HashSet pour une autre collection?
123456789 0

54
@LeoLuis: Cela rend le Containscontrôle rapide et vous garantit de n'évaluer la séquence qu'une seule fois.
Jon Skeet

2
@LeoLuis: Oui, la construction d'un HashSet à partir d'une séquence ne l'évalue qu'une seule fois. Je ne sais pas ce que vous entendez par «ensemble de collection faible».
Jon Skeet

2
@ AndréChristofferAndersen: Qu'entendez-vous par "dépassé"? Ça fonctionne encore. Si vous en avez un List<T>, c'est bien de l'utiliser.
Jon Skeet

4
@ AndréChristofferAndersen: Il serait préférable d'utiliserauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet

133

Il serait préférable d'utiliser List <T> .RemoveAll pour accomplir cela.

authorsList.RemoveAll((x) => x.firstname == "Bob");

8
@Reed Copsey: Le paramètre lambda dans votre exemple est placé entre parenthèses, c'est-à-dire (x). Y a-t-il une raison technique à cela? Est-ce considéré comme une bonne pratique?
Matt Davis

24
Non. Il est requis avec> 1 paramètre. Avec un seul paramètre, il est facultatif, mais il permet de conserver la cohérence.
Reed Copsey

48

Si vous avez vraiment besoin de supprimer des éléments, qu'en est-il d'Except ()?
Vous pouvez supprimer en fonction d'une nouvelle liste, ou supprimer à la volée en imbriquant le Linq.

var authorsList = new List<Author>()
{
    new Author{ Firstname = "Bob", Lastname = "Smith" },
    new Author{ Firstname = "Fred", Lastname = "Jones" },
    new Author{ Firstname = "Brian", Lastname = "Brains" },
    new Author{ Firstname = "Billy", Lastname = "TheKid" }
};

var authors = authorsList.Where(a => a.Firstname == "Bob");
authorsList = authorsList.Except(authors).ToList();
authorsList = authorsList.Except(authorsList.Where(a=>a.Firstname=="Billy")).ToList();

Except()est le seul moyen d'aller au milieu de l'instruction LINQ. IEnumerablen'a pas Remove()ni RemoveAll().
Jari Turkia

29

Vous ne pouvez pas le faire avec les opérateurs LINQ standard car LINQ fournit une requête et non une mise à jour du support.

Mais vous pouvez générer une nouvelle liste et remplacer l'ancienne.

var authorsList = GetAuthorList();

authorsList = authorsList.Where(a => a.FirstName != "Bob").ToList();

Ou vous pouvez supprimer tous les éléments authorsdans un deuxième passage.

var authorsList = GetAuthorList();

var authors = authorsList.Where(a => a.FirstName == "Bob").ToList();

foreach (var author in authors)
{
    authorList.Remove(author);
}

12
RemoveAll()n'est pas un opérateur LINQ.
Daniel Brückner

Mes excuses. Vous avez 100% raison. Malheureusement, je n'arrive pas à inverser mon downvote. Désolé pour ça.
Shai Cohen

Removeest également une méthode List< T>, pas une méthode System.Linq.Enumerable .
DavidRR

@Daniel, Corrigez-moi si je me trompe, nous pouvons éviter .ToList () d'où condition pour la deuxième option. C'est-à-dire que le code ci-dessous fonctionnera. var auteursList = GetAuthorList (); var auteurs = authorList.Where (a => a.FirstName == "Bob"); foreach (var auteur dans les auteurs) {authorList.Remove (auteur); }
Sai

Oui, cela fonctionnera. La transformer en liste n'est nécessaire que si vous avez besoin d'une liste pour la transmettre à une méthode ou si vous souhaitez ajouter ou supprimer d'autres éléments ultérieurement. Cela peut également être utile si vous devez énumérer la séquence plusieurs fois, car il vous suffit d'évaluer une fois la condition Where potentiellement coûteuse ou si le résultat peut changer entre deux énumérations, par exemple parce que la condition dépend de l'heure actuelle. Si vous ne souhaitez l'utiliser que dans une seule boucle, il n'est absolument pas nécessaire de d'abord stocker le résultat dans une liste.
Daniel Brückner

20

Solution simple:

static void Main()
{
    List<string> myList = new List<string> { "Jason", "Bob", "Frank", "Bob" };
    myList.RemoveAll(x => x == "Bob");

    foreach (string s in myList)
    {
        //
    }
}

comment supprimer "Bob" et "Jason" je veux dire plusieurs dans la liste de chaînes?
Néo

19

Je me demandais s'il y avait une différence entre RemoveAllet Exceptet les avantages de l'utilisation HashSet, j'ai donc effectué une vérification rapide des performances :)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace ListRemoveTest
{
    class Program
    {
        private static Random random = new Random( (int)DateTime.Now.Ticks );

        static void Main( string[] args )
        {
            Console.WriteLine( "Be patient, generating data..." );

            List<string> list = new List<string>();
            List<string> toRemove = new List<string>();
            for( int x=0; x < 1000000; x++ )
            {
                string randString = RandomString( random.Next( 100 ) );
                list.Add( randString );
                if( random.Next( 1000 ) == 0 )
                    toRemove.Insert( 0, randString );
            }

            List<string> l1 = new List<string>( list );
            List<string> l2 = new List<string>( list );
            List<string> l3 = new List<string>( list );
            List<string> l4 = new List<string>( list );

            Console.WriteLine( "Be patient, testing..." );

            Stopwatch sw1 = Stopwatch.StartNew();
            l1.RemoveAll( toRemove.Contains );
            sw1.Stop();

            Stopwatch sw2 = Stopwatch.StartNew();
            l2.RemoveAll( new HashSet<string>( toRemove ).Contains );
            sw2.Stop();

            Stopwatch sw3 = Stopwatch.StartNew();
            l3 = l3.Except( toRemove ).ToList();
            sw3.Stop();

            Stopwatch sw4 = Stopwatch.StartNew();
            l4 = l4.Except( new HashSet<string>( toRemove ) ).ToList();
            sw3.Stop();


            Console.WriteLine( "L1.Len = {0}, Time taken: {1}ms", l1.Count, sw1.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L2.Len = {0}, Time taken: {1}ms", l1.Count, sw2.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L3.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L4.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );

            Console.ReadKey();
        }


        private static string RandomString( int size )
        {
            StringBuilder builder = new StringBuilder();
            char ch;
            for( int i = 0; i < size; i++ )
            {
                ch = Convert.ToChar( Convert.ToInt32( Math.Floor( 26 * random.NextDouble() + 65 ) ) );
                builder.Append( ch );
            }

            return builder.ToString();
        }
    }
}

Résultats ci-dessous:

Be patient, generating data...
Be patient, testing...
L1.Len = 985263, Time taken: 13411.8648ms
L2.Len = 985263, Time taken: 76.4042ms
L3.Len = 985263, Time taken: 340.6933ms
L4.Len = 985263, Time taken: 340.6933ms

Comme nous pouvons le voir, la meilleure option dans ce cas est d'utiliser RemoveAll(HashSet)


Ce code: "l2.RemoveAll (new HashSet <string> (toRemove) .Contains);" ne devrait pas compiler ... et si vos tests sont corrects, ils appuient simplement ce que Jon Skeet a déjà suggéré.
Pascal

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );compile bien juste FYI
AzNjoE

9

C'est une très vieille question, mais j'ai trouvé un moyen très simple de le faire:

authorsList = authorsList.Except(authors).ToList();

Notez que puisque la variable de retour authorsListest un List<T>, le IEnumerable<T>retourné par Except()doit être converti en a List<T>.


7

Vous pouvez supprimer de deux manières

var output = from x in authorsList
             where x.firstname != "Bob"
             select x;

ou

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

var output = from x in authorsList
             where !authors.Contains(x) 
             select x;

J'ai eu le même problème, si vous voulez une sortie simple basée sur votre condition Where, la première solution est meilleure.


Comment puis-je vérifier "Bob" ou "Billy"?
Si8

6

Disons que authorsToRemovec'est un IEnumerable<T>qui contient les éléments que vous souhaitez supprimer authorsList.

Ensuite, voici un autre moyen très simple d'accomplir la tâche de suppression demandée par l'OP:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Je pense que vous pourriez faire quelque chose comme ça

    authorsList = (from a in authorsList
                  where !authors.Contains(a)
                  select a).ToList();

Bien que je pense que les solutions déjà données résolvent le problème d'une manière plus lisible.


4

Voici l'exemple pour supprimer l'élément de la liste.

 List<int> items = new List<int>() { 2, 2, 3, 4, 2, 7, 3,3,3};

 var result = items.Remove(2);//Remove the first ocurence of matched elements and returns boolean value
 var result1 = items.RemoveAll(lst => lst == 3);// Remove all the matched elements and returns count of removed element
 items.RemoveAt(3);//Removes the elements at the specified index

1

LINQ a ses origines dans la programmation fonctionnelle, qui met l'accent sur l'immuabilité des objets, donc il ne fournit pas un moyen intégré de mettre à jour la liste d'origine sur place.

Remarque sur l'immuabilité (tirée d'une autre réponse SO):

Voici la définition de l'immuabilité de Wikipédia .

Dans la programmation orientée objet et fonctionnelle, un objet immuable est un objet dont l'état ne peut pas être modifié après sa création.


0

Je pense que vous n'avez qu'à affecter les éléments de la liste des auteurs à une nouvelle liste pour prendre cet effet.

//assume oldAuthor is the old list
Author newAuthorList = (select x from oldAuthor where x.firstname!="Bob" select x).ToList();
oldAuthor = newAuthorList;
newAuthorList = null;

0

Pour garder le code fluide (si l'optimisation du code n'est pas cruciale) et vous devrez effectuer d'autres opérations sur la liste:

authorsList = authorsList.Where(x => x.FirstName != "Bob").<do_some_further_Linq>;

ou

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.