Utilizzo di LINQ per rimuovere elementi da un elenco <T>


655

Dì che ho una query LINQ come:

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

Dato che authorsListè di tipo List<Author>, come posso eliminare gli Authorelementi da authorsListcui vengono restituiti dalla query authors?

O, per dirla in altro modo, come posso eliminare tutto il nome di Bob uguale a nome authorsList?

Nota: questo è un esempio semplificato ai fini della domanda.

Risposte:


1139

Bene, sarebbe più facile escluderli in primo luogo:

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

Tuttavia, ciò cambierebbe semplicemente il valore authorsListinvece di rimuovere gli autori dalla raccolta precedente. In alternativa, puoi usare RemoveAll:

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

Se hai davvero bisogno di farlo sulla base di un'altra raccolta, userei un HashSet, RemoveAll e Contiene:

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

14
Qual è la ragione per usare HashSet per un'altra collezione?
123 456 789 0

54
@LeoLuis: velocizza il Containscontrollo e ti assicura di valutare la sequenza solo una volta.
Jon Skeet,

2
@LeoLuis: Sì, la creazione di un HashSet da una sequenza lo valuta solo una volta. Non sono sicuro di cosa intendi per "set di raccolta debole".
Jon Skeet,

2
@ AndréChristofferAndersen: Cosa intendi con "obsoleto"? Funziona ancora. Se hai un List<T>, va bene usarlo.
Jon Skeet,

4
@ AndréChristofferAndersen: sarebbe meglio usarloauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet,

133

Sarebbe meglio usare List <T> .RemoveAll per raggiungere questo obiettivo.

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

8
@Reed Copsey: il parametro lambda nel tuo esempio è racchiuso tra parentesi, ovvero (x). C'è un motivo tecnico per questo? È considerata una buona pratica?
Matt Davis,

24
No. È richiesto con> 1 parametro. Con un singolo parametro, è facoltativo, ma aiuta a mantenere la coerenza.
Reed Copsey,

48

Se hai davvero bisogno di rimuovere elementi, che dire di Except ()?
È possibile rimuovere in base a un nuovo elenco o rimuovere al volo annidando 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()è l'unico modo per andare nel mezzo dell'istruzione LINQ. IEnumerablenon ha Remove()RemoveAll().
Jari Turkia,

29

Non è possibile farlo con operatori LINQ standard poiché LINQ fornisce query, non supporto per l'aggiornamento.

Ma puoi generare un nuovo elenco e sostituire quello vecchio.

var authorsList = GetAuthorList();

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

Oppure puoi rimuovere tutti gli elementi authorsin un secondo passaggio.

var authorsList = GetAuthorList();

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

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

12
RemoveAll()non è un operatore LINQ.
Daniel Brückner,

Mie scuse. Hai ragione al 100%. Sfortunatamente, non riesco a invertire il mio voto negativo. Mi dispiace per quello.
Shai Cohen,

Removeè anche un metodo List< T>, non un metodo System.Linq.Enumerable .
DavidRR

@Daniel, correggimi se sbaglio possiamo evitare .ToList () da dove condtion per la seconda opzione. Cioè sotto il codice funzionerà. var autoriList = GetAuthorList (); var autori = autoriList.Where (a => a.FirstName == "Bob"); foreach (var author in autori) {authorList.Remove (autore); }
Sai,

Sì, funzionerà. Trasformarlo in un elenco è necessario solo se è necessario un elenco per passarlo a un metodo o se si desidera aggiungere o rimuovere più elementi in un secondo momento. Può anche essere utile se è necessario enumerare la sequenza più volte perché in questo caso è necessario valutare una volta la condizione potenzialmente cara solo una volta o se il risultato può cambiare tra due enumerazioni, ad esempio perché la condizione dipende dall'ora corrente. Se si desidera utilizzarlo solo in un ciclo, non è assolutamente necessario memorizzare prima il risultato in un elenco.
Daniel Brückner,

20

Soluzione semplice:

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

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

come rimuovere "Bob" e "Jason" intendo multipli nella lista di stringhe?
Neo

19

Mi chiedevo se ci fosse qualche differenza tra RemoveAlle Excepte i pro dell'utilizzo HashSet, quindi ho fatto un rapido controllo delle prestazioni :)

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

Risultati di seguito:

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

Come possiamo vedere, la migliore opzione in quel caso è usare RemoveAll(HashSet)


Questo codice: "l2.RemoveAll (new HashSet <string> (toRemove) .Contains);" non dovrebbe essere compilato ... e se i tuoi test sono corretti, sono solo secondo ciò che Jon Skeet ha già suggerito.
Pascal,

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );compila bene solo FYI
AzNjoE

9

Questa è una domanda molto vecchia, ma ho trovato un modo davvero semplice per farlo:

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

Si noti che poiché la variabile return authorsListè a List<T>, il IEnumerable<T>restituito da Except()deve essere convertito in a List<T>.


7

Puoi rimuovere in due modi

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

o

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

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

Ho avuto lo stesso problema, se si desidera un output semplice in base alle proprie condizioni, la prima soluzione è migliore.


Come posso verificare "Bob" o "Billy"?
Si8,

6

Supponiamo che authorsToRemovesia un IEnumerable<T>elemento che contiene gli elementi da rimuovere authorsList.

Quindi ecco un altro modo molto semplice per eseguire l'attività di rimozione richiesta dall'OP:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Penso che potresti fare qualcosa del genere

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

Anche se penso che le soluzioni già fornite risolvano il problema in un modo più leggibile.


4

Di seguito è riportato l'esempio per rimuovere l'elemento dall'elenco.

 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 ha le sue origini nella programmazione funzionale, che enfatizza l'immutabilità degli oggetti, quindi non fornisce un modo integrato per aggiornare sul posto l'elenco originale.

Nota sull'immutabilità (presa da un'altra risposta SO):

Ecco la definizione di immutabilità da Wikipedia .

Nella programmazione orientata agli oggetti e funzionale, un oggetto immutabile è un oggetto il cui stato non può essere modificato dopo la sua creazione.


0

penso che devi solo assegnare gli elementi dall'elenco degli autori a un nuovo elenco per ottenere tale effetto.

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

0

Per mantenere il codice fluente (se l'ottimizzazione del codice non è cruciale) e dovresti fare alcune ulteriori operazioni nell'elenco:

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

o

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.