2012-01-09 11 views
9

Por ahora, lo mejor que podía pensar es:eliminando eficazmente elemento desde 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; 
    } 
} 

Yo sé que tengo al menos una variable adicional aquí, pero se incluye para mejorar la legibilidad de el algoritmo.

+0

posible duplicado de [Cómo eliminar condicionalmente artículos de una colección .NET] (http://stackoverflow.com/questions/653596/how-to-conditionally-remove-items-from-a-net- colección) –

Respuesta

31

El método "RemoveAll" es el mejor.

Otra técnica común es:

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

Otra técnica común es utilizar un bucle "for", pero asegúrese de ir hacia atrás:

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

Otra técnica común es añadir los artículos que son no que se están quitando a una nueva colección:

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

Y ahora tiene dos colecciones. Una técnica que me gusta particularmente si quieres terminar con dos colecciones es usar estructuras de datos inmutables. Con una estructura de datos inmutable, "eliminar" un elemento no cambia la estructura de datos; le devuelve una nueva estructura de datos (que reutiliza bits del anterior, si es posible) que no tiene el elemento que eliminó. Con estructuras de datos inmutables que no está modificando lo que estás interactuando sobre, así que no hay 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); 

Y cuando haya terminado, usted tiene dos colecciones. El nuevo tiene los elementos eliminados, el anterior es el mismo que nunca.

+0

¿Alguna vez ha utilizado la extensión 'Reverse' utilizando un foreach? Acabo de encontrarlo aquí http://stackoverflow.com/a/10541025/706363? ¿Por qué no sería una opción más utilizada? ¿Hay algún tipo de implicaciones graves en el rendimiento o algo así? – ppumkin

+0

@ppumkin: Intente escribir una implementación de 'Reverse' para un' IEnumerable' que no implemente 'IList',' ICollection', y así sucesivamente. ¿Cuál es el rendimiento de memoria y tiempo de su implementación? –

+0

Mi implementación no es crítica en cuanto a tiempo o recursos. Estoy usando la extensión 'IEnumerable.Reverse ' en Linq on a List, y parece funcionar bien en un 'foreach' - Es por eso que pregunto, ¿por qué no se usa este ejemplo con más frecuencia, en lugar de todo esto para (i a 0) en cambio, como en tu respuesta. ¿Está utilizando la extensión inversa una opción válida? ¿Puedes agregarlo a tu respuesta o hay algún problema con el uso de la extensión inversa de Linq en combinación con foreach? Solo pensé que su opinión hará que tenga más peso debido a su experiencia. – ppumkin

13

Justo cuando terminé de escribir recordé que hay una forma de hacerlo.

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

¿Mejor manera?

+2

Solo un FYI: esto se puede convertir a un grupo de métodos. collection.RemoveAll (ShouldBeDeleted). – ahawker

1

La forma lambda es buena. También podría usar un ciclo for para regular, puede repetir listas que un bucle for usa dentro del bucle en sí, a diferencia de un bucle foreach.

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

Estoy asumiendo que la recolección es un ArrayList aquí, el código podría ser un poco diferente si está utilizando una estructura de datos diferente.

1

No se puede eliminar de una colección dentro de un bucle foreach (a menos que sea una colección muy especial que tenga un enumerador especial). Las colecciones de BCL generarán excepciones si la colección se modifica mientras se enumera.

Puede utilizar un bucle for para eliminar elementos individuales y ajustar el índice en consecuencia. Sin embargo, hacer eso puede ser propenso a errores. Dependiendo de la implementación de la colección subyacente, también puede ser costoso eliminar elementos individuales. Por ejemplo, borrar el primer elemento de un List<T> copiará todos los elementos remanentes en la lista.

La mejor solución es a menudo para crear una nueva colección basada en la edad:

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

Uso ToList() o ToArray() para crear la nueva colección o inicializar el tipo de colección específica de la IEnumerable devuelto por la cláusula Where().

1

Una variación hacia adelante en el bucle hacia atrás for:

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

sólo tiene que utilizar dos listas,

Primera lista es la lista original y segunda lista es para los artículos que no deben ser removidos.

var filteredItems = new List<ItemType>(); 

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