Estoy buscando un mejor patrón para trabajar con una lista de elementos que cada uno necesita procesarse y luego, según el resultado, se eliminan de la lista.
No puede usar .Remove(element)
dentro de foreach (var element in X)
(porque da como resultado una Collection was modified; enumeration operation may not execute.
excepción) ... tampoco puede usar for (int i = 0; i < elements.Count(); i++)
y .RemoveAt(i)
porque interrumpe su posición actual en la colección en relación con i
.
¿Hay alguna forma elegante de hacer esto?
list.RemoveAll(Function(item) item.Value = somevalue)
.size()
lugar de.Count
y en.remove(i)
lugar de.removeAt(i)
. Clever - gracias!RemoveAll()
lleva tres veces más tiempo que elfor
ciclo hacia atrás . Así que definitivamente me quedo con el ciclo, al menos en las secciones donde importa.Una solución simple y directa:
Use un bucle for estándar que se ejecute al revés en su colección y
RemoveAt(i)
para eliminar elementos.fuente
La iteración inversa debe ser lo primero que se le viene a la mente cuando desea eliminar elementos de una Colección mientras itera sobre ella.
Afortunadamente, hay una solución más elegante que escribir un bucle for que implica escribir innecesariamente y puede ser propenso a errores.
fuente
Reverse<T>()
crea un iterador que recorre la lista al revés, pero asigna un búfer adicional con el mismo tamaño que la propia lista para él ( referencesource.microsoft.com/#System.Core/System/Linq/… ).Reverse<T>
no solo revisa la lista original en orden inverso (sin asignar memoria adicional). Por lo tanto, tantoToList()
yReverse()
tener el mismo consumo de memoria (tanto para crear la copia), peroToList()
no hace nada a los datos. ConReverse<int>()
, me pregunto por qué se invierte la lista, por qué motivo.Reverse<T>()
cree un nuevo búfer, no estoy seguro de entender por qué es necesario. Me parece que dependiendo de la estructura subyacente de laEnumerable
, al menos en algunos casos debería ser posible lograr la iteración inversa sin asignar memoria lineal.Si agrega ".ToList ()" a su lista (o los resultados de una consulta LINQ), puede eliminar el "elemento" directamente de "lista" sin la temida " Colección modificada; la operación de enumeración puede no ejecutarse ". error. El compilador hace una copia de "lista", para que pueda eliminar de forma segura la matriz.
Si bien este patrón no es súper eficiente, tiene una sensación natural y es lo suficientemente flexible para casi cualquier situación . Por ejemplo, cuando desea guardar cada "elemento" en un DB y eliminarlo de la lista solo cuando el guardado de DB tiene éxito.
fuente
Seleccione los elementos que no desea en lugar de tratar de eliminar los elementos que no desee. Esto es mucho más fácil (y generalmente más eficiente también) que eliminar elementos.
Quería publicar esto como un comentario en respuesta al comentario dejado por Michael Dillon a continuación, pero de todos modos es demasiado largo y probablemente útil para tener en mi respuesta:
Personalmente, nunca eliminaría los elementos uno por uno, si necesita eliminarlos, llame a
RemoveAll
que toma un predicado y solo reorganiza la matriz interna una vez, mientras queRemove
realiza unaArray.Copy
operación para cada elemento que elimina.RemoveAll
Es mucho más eficiente.Y cuando está iterando hacia atrás sobre una lista, ya tiene el índice del elemento que desea eliminar, por lo que sería mucho más eficiente llamar
RemoveAt
, porqueRemove
primero hace un recorrido de la lista para encontrar el índice del elemento que desea está intentando eliminar, pero ya conoce ese índice.En general, no veo ninguna razón para llamar
Remove
a un bucle for. E idealmente, si es posible, use el código anterior para transmitir elementos de la lista según sea necesario para que no se tenga que crear una segunda estructura de datos.fuente
El uso de ToArray () en una lista genérica le permite eliminar (elemento) en su lista genérica:
fuente
El uso de .ToList () hará una copia de su lista, como se explica en esta pregunta: ToList (): ¿crea una nueva lista?
Al usar ToList (), puede eliminar de su lista original, porque en realidad está iterando sobre una copia.
fuente
Si la función que determina qué elementos eliminar no tiene efectos secundarios y no muta el elemento (es una función pura), una solución simple y eficiente (tiempo lineal) es:
Si hay efectos secundarios, usaría algo como:
Todavía es tiempo lineal, suponiendo que el hash sea bueno. Pero tiene un mayor uso de memoria debido al hashset.
Finalmente, si su lista es solo una en
IList<T>
lugar de unaList<T>
, sugiero mi respuesta a ¿Cómo puedo hacer este iterador foreach especial? . Esto tendrá un tiempo de ejecución lineal dadas implementaciones típicas deIList<T>
, en comparación con el tiempo de ejecución cuadrático de muchas otras respuestas.fuente
Como cualquier eliminación se toma bajo una condición que puede usar
fuente
fuente
No puede usar foreach, pero puede iterar hacia adelante y administrar su variable de índice de bucle cuando elimina un elemento, de esta manera:
Tenga en cuenta que, en general, todas estas técnicas se basan en el comportamiento de la colección que se repite. La técnica que se muestra aquí funcionará con la Lista estándar (T). (Es muy posible que escribir su propia clase de colección y iterador que hace permitir la extracción elemento durante un bucle foreach.)
fuente
Usar
Remove
oRemoveAt
en una lista mientras se itera sobre esa lista se ha hecho intencionalmente difícil, porque casi siempre es algo incorrecto . Es posible que pueda hacer que funcione con algún truco inteligente, pero sería extremadamente lento. Cada vez que llamaRemove
tiene que escanear toda la lista para encontrar el elemento que desea eliminar. Cada vez que llameRemoveAt
tiene que mover elementos posteriores 1 posición a la izquierda. Como tal, cualquier solución que useRemove
oRemoveAt
, requeriría tiempo cuadrático, O (n²) .Úselo
RemoveAll
si puede. De lo contrario, el siguiente patrón filtrará la lista in situ en tiempo lineal, O (n) .fuente
Me gustaría que el "patrón" era algo como esto:
Esto alinearía el código con el proceso que continúa en el cerebro del programador.
fuente
Nullable<bool>
, si desea permitir sin marcar), luego úsela después del foreach para eliminar / conservar elementos.Suponiendo que el predicado es una propiedad booleana de un elemento, que si es verdadero, entonces el elemento debe eliminarse:
fuente
Reasignaría la lista de una consulta LINQ que filtró los elementos que no deseaba conservar.
A menos que la lista sea muy grande, no debería haber problemas de rendimiento significativos al hacer esto.
fuente
La mejor manera de eliminar elementos de una lista mientras se repite es utilizarlos
RemoveAll()
. Pero la principal preocupación escrita por las personas es que tienen que hacer algunas cosas complejas dentro del ciclo y / o tener casos de comparación complejos.La solución es seguir usando,
RemoveAll()
pero use esta notación:fuente
Simplemente cree una lista completamente nueva a partir de la primera. Digo "Fácil" en lugar de "Correcto", ya que crear una lista completamente nueva probablemente tenga un rendimiento superior al método anterior (no me he molestado con ninguna evaluación comparativa). Generalmente prefiero este patrón, también puede ser útil para superar Limitaciones de Linq-To-Entities.
De esta forma, se desplaza la lista hacia atrás con un bucle For antiguo. Hacer esto hacia adelante podría ser problemático si el tamaño de la colección cambia, pero hacia atrás siempre debe ser seguro.
fuente
Copie la lista que está iterando. Luego elimine de la copia e interate el original. Retroceder es confuso y no funciona bien cuando se realiza un bucle en paralelo.
fuente
Me gustaria esto
SALIDA
fuente
Me encontré en una situación similar en la que tuve que eliminar cada enésimo elemento de un determinado
List<T>
.fuente
El costo de eliminar un artículo de la lista es proporcional al número de artículos que siguen al que se eliminará. En el caso de que la primera mitad de los elementos califique para su eliminación, cualquier enfoque que se base en la eliminación individual de elementos terminará teniendo que realizar aproximadamente N * N / 4 operaciones de copia de elementos, lo que puede ser muy costoso si la lista es grande .
Un enfoque más rápido es escanear a través de la lista para encontrar el primer elemento que se eliminará (si lo hay) y, a partir de ese momento, copiar cada elemento que se debe retener en el lugar al que pertenece. Una vez hecho esto, si se deben conservar los elementos R, los primeros elementos R de la lista serán esos elementos R, y todos los elementos que requieren eliminación estarán al final. Si esos elementos se eliminan en orden inverso, el sistema no terminará teniendo que copiar ninguno de ellos, por lo que si la lista tenía N elementos de los cuales R elementos, incluidos todos los primeros F, se conservaron, será necesario copie elementos RF y reduzca la lista por un elemento NR veces. Todo el tiempo lineal.
fuente
Mi enfoque es que primero creo una lista de índices, que deberían eliminarse. Luego, recorro los índices y elimino los elementos de la lista inicial. Esto se ve así:
fuente
En C #, una manera fácil es marcar las que desea eliminar y luego crear una nueva lista para repetir ...
o incluso más simple usar linq ...
pero vale la pena considerar si otras tareas o subprocesos tendrán acceso a la misma lista al mismo tiempo que está ocupado eliminando, y tal vez use una lista concurrente.
fuente
Rastree los elementos que se eliminarán con una propiedad y elimínelos todos después del proceso.
fuente
Solo quería agregar mis 2 centavos a esto en caso de que esto ayude a alguien, tuve un problema similar pero necesitaba eliminar varios elementos de una lista de matriz mientras se repetía. la respuesta más votada lo hizo por mí en su mayor parte hasta que me encontré con errores y me di cuenta de que el índice era mayor que el tamaño de la lista de la matriz en algunos casos porque se eliminaban varios elementos pero el índice del bucle no se mantenía seguimiento de eso. Lo arreglé con una simple verificación:
fuente
fuente
simples;
aquí?