c# list iteration

c# - ¿La mejor manera de iterar sobre una lista y eliminar elementos de ella?



iteration (5)

Necesito iterar sobre una List<myObject> y eliminar los elementos que responden a una determinada condición.

Vi esta respuesta ( https://stackoverflow.com/a/1582317/5077434 ):

Iterar su lista al revés con un bucle for:

for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); }

Ejemplo:

var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i));

Pero entendí que for es menos eficiente que for cada

Así que pensé en usar el siguiente como sigue:

foreach (var item in myList.ToList()) { // if certain condition applies: myList.Remove(item) }

¿Es un método mejor que el otro?

EDITAR:

No quiero usar RemoveAll(...) , ya que hay una gran cantidad de código dentro del bucle, antes de la condición.


El problema es que foreach no puede trabajar con la colección mientras se está modificando, esto llevará a InvalidOperationException .

El uso de .ToList() dentro de foreach nos permite evitarlo, pero esto copia la colección completa, lo que se traduce en un bucle a través de la colección dos veces (la primera vez cuando copia todos los elementos y la segunda vez para filtrarla).

El bucle for es la mejor opción porque podemos pasar por la recopilación una vez. Aparte de eso, puede filtrar la colección usando el método LINQ Where() , algo entre las líneas de:

var myFilteredCollection = myList.Where(i => i <= 5).ToList();

Si sus condiciones son complejas, puede mover esta lógica a un método separado:

private bool IsItemOk(myObject item) { ... your complicated logic. return true; }

Y utilízalo en tu consulta LINQ:

var myFilteredCollection = myList.Where(i => IsItemOk(i)).ToList();

Otra opción es hacerlo viceversa. Crear nueva colección y agregar elementos basados ​​en la condición. De lo que puede usar foreach loop:

var newList = new List<MyObject>(myList.Count); foreach (var item in myList) { // if certain condition does not apply: newList.Add(item); }

PD

Pero como ya se mencionó .RemoveAll() es mejor porque de esta manera no tienes que ''reinventar la rueda''


Ninguno de los métodos muestra diferencias significativas. for y RemoveAll parece un poco más rápido (lo más probable es que al usar Remove está duplicando su lista y necesite recrearla ... usando tipos de valor, esto también significa duplicar la memoria necesaria). Hice un poco de perfil

public static void MethodA() { var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } } public static void MethodB() { var list = new List<int>(Enumerable.Range(1, 10)); foreach (var item in list.ToList()) { if (item > 5) list.Remove(item); } } public static void MethodC() { var list = new List<int>(Enumerable.Range(1, 10)); list.RemoveAll(x => x > 5); } public static void Measure(string meas, Action act) { GC.Collect(); GC.WaitForPendingFinalizers(); var st = new Stopwatch(); st.Start(); for (var i = 0; i < 10000000; i++) act(); st.Stop(); Console.WriteLine($"{meas}: {st.Elapsed}"); } static void Main(string[] args) { Measure("A", MethodA); Measure("B", MethodB); Measure("C", MethodC); }

El resultado:

A: 00: 00: 04.1179163

B: 00: 00: 07.7417853

C: 00: 00: 04.3525255

Otras ejecuciones dan resultados aún más similares (tenga en cuenta que esto es para 10 millones de iteraciones en una lista de 10 elementos [haciendo así 100 millones de operaciones] ... una diferencia de ~ 3 segundos no es lo que yo llamaría "significativo", pero su el kilometraje puede variar)

Vaya con lo que cree que puede leer mejor (yo personalmente usaría el método RemoveAll )

Nota : a medida que aumente su lista (esto es solo 10 elementos), el MethodB (el único que duplica la lista) será más lento y las diferencias serán mayores, ya que la duplicación de la lista será más costosa. Por ejemplo, al hacer una lista de 1,000 artículos (con la condición en 500 en lugar de 5) y 100,000 iteraciones, el MethodB 34 segundos, mientras que los otros dos funcionan en menos de 2 segundos en mi máquina. Así que sí, definitivamente no uses MethodB :-)

Esto no significa que foreach sea ​​menos eficiente que for , solo significa que para usar foreach para eliminar elementos de una lista, debe duplicar dicha lista, y eso es costoso.

Este otro método (usando foreach ) debería ser tan rápido como los métodos for o RemoveAll (es un poco más lento en mi perfil, porque asigna una nueva lista, me imagino), pero bastante insignificante):

public static void MethodD() { var originalList = new List<int>(Enumerable.Range(1, 1000)); var list = new List<int>(originalList.Count); foreach (var item in originalList) { if (item <= 500) list.Add(item); } list.Capacity = list.Count; }

Solo necesitas revertir la condición.

No estoy sugiriendo este método, solo estoy probando que foreach no es necesariamente más lento que for


Otra forma podría ser obtener la lista, establecer el elemento en nulo y luego hacer una eliminación de linq a (elemento => nulo), como ya se dijo, depende de usted elegir la forma que más le convenga.


Willy-nilly, tiene que recorrer la lista, y el bucle for es el más eficiente:

for (int i = safePendingList.Count - 1; i >= 0; --i) if (condition) safePendingList.RemoveAt(i);

Si desea eliminar dentro del rango (no en la lista completa ), simplemente modifique para bucle:

// No Enumarable.Range(1, 10) - put them into "for" for (int i = Math.Min(11, safePendingList.Count - 1); i >= 1; --i) if (condition) safePendingList.RemoveAt(i);

O si tiene que eliminar elementos en bucle hacia adelante :

for (int i = 0; i < safePendingList.Count;) // notice ++i abscence if (condition) safePendingList.RemoveAt(i); else i += 1; // ++i should be here

Por el contrario, safePendingList.ToList() crea una copia de safePendingList inicial y esto significa memoria y sobrecarga de CPU :

// safePendingList.ToList() - CPU and Memory overhead (copying) foreach (var item in safePendingList.ToList()) { if (condition) myList.Remove(item); // Overhead: searching }

Sin embargo, el plan más razonable en muchos casos es dejar que .Net trabaje para usted:

safePendingList.RemoveAll(item => condition);


para eliminar elementos con una determinada condición que puede utilizar

list.RemoveAll(item => item > 5);

en lugar de

var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); }