2012-01-09 5 views
9

Per ora, il meglio che ho potuto pensare è:efficiente l'eliminazione di voce dal di dentro 'foreach'

bool oneMoreTime = true; 
while (oneMoreTime) 
{ 
    ItemType toDelete=null; 
    oneMoreTime=false; 
    foreach (ItemType item in collection) 
    { 
     if (ShouldBeDeleted(item)) 
     { 
      toDelete=item; 
      break; 
     } 
    } 
    if (toDelete!=null) 
    { 
     collection.Remove(toDelete); 
     oneMoreTime=true; 
    } 
} 

So che ho almeno una variabile in più qui, ma ho incluso per migliorare la leggibilità del l'algoritmo.

+0

possibile duplicato di [Come rimuovere condizionalmente elementi da una collezione NET] (http://stackoverflow.com/questions/653596/how-to-conditionally-remove-items-from-a-net- collezione) –

risposta

31

Il metodo "RemoveAll" è meglio.

Un'altra tecnica comune è:

var itemsToBeDeleted = collection.Where(i=>ShouldBeDeleted(i)).ToList(); 
foreach(var itemToBeDeleted in itemsToBeDeleted) 
    collection.Remove(itemToBeDeleted); 

Un'altra tecnica comune è quella di utilizzare un ciclo "for", ma assicuratevi di andare all'indietro:

for (int i = collection.Count - 1; i >= 0; --i) 
    if (ShouldBeDeleted(collection[i])) 
     collection.RemoveAt(i); 

Un'altra tecnica comune è quella di aggiungere gli articoli che sono non vengono rimossi in una nuova raccolta:

var newCollection = new List<whatever>(); 
foreach(var item in collection.Where(i=>!ShouldBeDeleted(i)) 
    newCollection.Add(item); 

e ora avete due collezioni. Una tecnica che mi piace particolarmente se vuoi finire con due raccolte è usare strutture di dati immutabili. Con una struttura dati immutabile, la "rimozione" di un elemento non modifica la struttura dei dati; ti restituisce una nuova struttura dati (che riutilizza i bit dal vecchio, se possibile) che non ha l'oggetto rimosso. Con strutture di dati immutabili che non sta modificando la cosa si sta scorrendo sopra, quindi non c'è nessun problema:

var newCollection = oldCollection; 
foreach(var item in oldCollection.Where(i=>ShouldBeDeleted(i)) 
    newCollection = newCollection.Remove(item); 

o

var newCollection = ImmutableCollection<whatever>.Empty; 
foreach(var item in oldCollection.Where(i=>!ShouldBeDeleted(i)) 
    newCollection = newCollection.Add(item); 

E quando hai finito, si dispone di due collezioni. Il nuovo ha gli oggetti rimossi, il vecchio è lo stesso di sempre.

+0

Hai mai usato l'estensione 'Reverse' usando un foreach? L'ho appena trovato qui http://stackoverflow.com/a/10541025/706363? Perché non dovrebbe essere un'opzione più ampiamente utilizzata? C'è qualche tipo di serie implicazioni sulle prestazioni o qualcosa che succede lì? – ppumkin

+0

@ppumkin: prova a scrivere un'implementazione di 'Reverse' per un' IEnumerable' che non implementa 'IList',' ICollection' e così via. Qual è la memoria e le prestazioni temporali della tua implementazione? –

+0

La mia implementazione non è tempo o risorsa critica. Sto usando l'estensione 'IEnumerable.Reverse ' in Linq su una lista, e sembra funzionare bene in un' foreach' - Ecco perché chiedo, perché non viene usato un esempio più frequentemente, a differenza di tutti questi per (I a 0) al contrario invece, come nella tua risposta. Sta usando Reverse Extension un'opzione valida? Puoi aggiungerlo alla tua risposta o c'è qualcosa di sbagliato nell'usare l'estensione inversa di Linq in combinazione con foreach? Ho solo pensato che la tua opinione avrà maggior peso a causa della tua esperienza. – ppumkin

13

Così come ho finito di scrivere mi sono ricordato che c'è lambda-modo per farlo.

collection.RemoveAll(i=>ShouldBeDeleted(i)); 

Un modo migliore?

+2

Solo una FYI: può essere convertita in un gruppo di metodi. collection.RemoveAll (ShouldBeDeleted). – ahawker

1

Il lambda modo è buono. È anche possibile utilizzare un ciclo for normale, è possibile iterare gli elenchi utilizzati da un loop all'interno del ciclo stesso, a differenza di un ciclo foreach.

for (int i = collection.Count-1; i >= 0; i--) 
{ 
    if(ShouldBeDeleted(collection[i]) 
     collection.RemoveAt(i); 
} 

Io parto dal presupposto che la raccolta è un ArrayList qui, il codice potrebbe essere un po 'diverso se si utilizza una struttura dati diversa.

1

Non è possibile eliminare da una raccolta all'interno di un ciclo foreach (a meno che non si tratti di una raccolta molto speciale con un enumeratore speciale). Le raccolte BCL generano eccezioni se la raccolta viene modificata mentre viene enumerata.

Si potrebbe utilizzare un ciclo for eliminare singoli elementi e regolare l'indice di conseguenza. Tuttavia, farlo può essere soggetto a errori. A seconda dell'implementazione della raccolta sottostante, potrebbe anche essere costoso eliminare singoli elementi. Per esempio eliminando il primo elemento di un List<T> copierà tutti gli elementi remaning nell'elenco.

La soluzione migliore è spesso quello di creare una nuova collezione basata sul vecchio:

var newCollection = collection.Where(item => !ShouldBeDeleted(item)).ToList(); 

Usa ToList() o ToArray() per creare la nuova collezione o inizializzare il tipo di raccolta specifico dal IEnumerable restituito dalla clausola di Where().

1

Una variazione in avanti sul rovescio for ciclo:

for (int i = 0; i < collection.Count;) 
    if (ShouldBeDeleted(collection[i])) 
     collection.RemoveAt(i) 
    else 
     i++; 
0

Basta usare lista di secondo,

prima lista è la vostra lista originale e Seconda lista è per gli elementi che non devono essere rimossi.

var filteredItems = new List<ItemType>(); 

foreach(var item in collection){ 
    if(!ShouldBeDeleted(item)) 
     filteredItems.Add(item); 
}