2009-05-12 14 views
509

Dire che ho query LINQ come ad esempio:utilizzando LINQ per rimuovere elementi da un elenco <T>

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

Dato che authorsList è di tipo List<Author>, come posso eliminare i Author elementi authorsList che vengono restituiti dal interrogare su authors?

Oppure, per dirla in un altro modo, come è possibile eliminare tutti gli equivalenti Bob del nome da authorsList?

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

risposta

887

Beh, sarebbe più facile per escluderli, in primo luogo:

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

tuttavia, che sarebbe solo cambiare il valore di authorsList invece di rimuovere gli autori della collezione precedente. In alternativa, è possibile utilizzare RemoveAll:

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

Se avete veramente 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)); 
+7

Qual è il motivo dell'utilizzo di HashSet per un'altra raccolta? –

+40

@LeoLuis: rende veloce il controllo 'Contains' e garantisce di valutare la sequenza solo una volta. –

+0

È bello sapere. Deve essere davvero un HashSet, però, un set di raccolta debole andrà bene? Infine, sei sicuro che valuti solo una volta la sequenza? –

19

Non si può fare questo con lo standard Operatori LINQ perché LINQ fornisce query, non aggiorna il supporto.

Ma è possibile generare un nuovo elenco e sostituire quello vecchio.

var authorsList = GetAuthorList(); 

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

Oppure si potrebbe rimuovere tutti gli elementi in authors in un secondo passaggio.

var authorsList = GetAuthorList(); 

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

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

errato. 'RemoveAll()' ** fa ** aggiorna la lista sul posto. –

+7

'RemoveAll()' non è un operatore LINQ. –

+0

Le mie scuse. Sei corretto al 100% Sfortunatamente, non riesco a invertire il mio downvote. Mi dispiace per quello –

108

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

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

@Reed Copsey: il parametro lambda nell'esempio è racchiuso tra parentesi, ovvero (x). C'è una ragione tecnica per questo? E 'considerato una buona pratica? –

+17

No. È richiesto con il parametro> 1. Con un singolo parametro, è opzionale, ma aiuta a mantenere la coerenza. –

0

LINQ ha origine nella programmazione funzionale, che sottolinea immutabilità di oggetti, in modo che non fornisce un modo incorporato per aggiornare l'elenco originale sul posto.

Nota sulla immutabilità (preso da un altro SO risponde):

Ecco la definizione di immutabilità da Wikipedia (link)

"In object-oriented e funzionale programmazione, un oggetto immutabile, è un oggetto la cui lo stato non può essere modificato dopo che è stato creato. "

17

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) 
    { 
     // 
    } 
} 
3

Penso che si potrebbe fare qualcosa di simile

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

Anche se credo che le soluzioni già date a risolvere il problema in modo più leggibile.

5

È possibile 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 che l'uscita semplice, sulla base di dove condizione, allora prima soluzione è migliore.

+0

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

33

Se è davvero necessario rimuovere gli elementi, allora che dire di Eccezione()?
È possibile rimuovere in base a un nuovo elenco o rimuovere al volo annidando il 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(); 
6

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

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

Nota che, poiché la variabile di ritorno authorsList è un List<T>, il IEnumerable<T> restituito da Except() deve essere convertito in List<T>.

4

Dire che authorsToRemove è un IEnumerable<T> che contiene gli elementi che si desidera rimuovere da authorsList.

Poi qui è un altro modo molto semplice per realizzare il compito rimozione chiesto dal PO:

authorsList.RemoveAll(authorsToRemove.Contains); 
12

mi aggiravo, se ci sono delle differenze tra RemoveAll e Salvo e vantaggi dell'utilizzo di HashSet, quindi non ho fatto di controllo delle prestazioni rapido :)

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 qui sotto:

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, opzione migliore in questo caso è quello di utilizzare RemoveAll (HashSet)

+0

Questo codice: "l2.RemoveAll (new HashSet (toRemove) .Contains);" non dovrei compilare ... e se i tuoi test sono corretti, sono solo secondi rispetto a quanto già suggerito da Jon Skeet. – Pascal

+1

'l2.RemoveAll (new HashSet (toRemove) .Contains);' compila bene solo FYI – AzNjoE

0

penso che basta per assegnare gli elementi dalla lista Autore di un nuovo elenco di prendere in tal senso.

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

è molto semplice:

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

Perché hai scritto questa risposta 6 anni dopo la risposta accettata, che contiene già ciò che stai dicendo? – EluciusFTW

2

Di seguito si riporta l'esempio di 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 
Problemi correlati