Este es un ejemplo: originalList es una lista de objetos
var subList = (originalList.Where(x => x.number < 0)).ToList(); originalList.RemoveAll(x => x.number < 0); Usaré la subList más tarde. En este ejemplo originaList se itera dos veces. Esta función se llama miles de millones de veces y originalList es una lista grande
¿Hay una manera fácil de mejorar el rendimiento?
Una cosa importante: el valor del número del objeto puede cambiar entre dos llamadas de esta función.
Este método elimina todos los elementos que cumplen una condición y devuelve una lista de los elementos eliminados. Solo itera una vez.
public static List<T> RemoveAll<T>(List<T> input, Func<T,bool> condition) { List<T> removedEntries = new List<T>(); int offset = 0; for(int i = 0; i < input.Count - offset; i++) { while(i < input.Count - offset && condition.Invoke(input[i + offset])) { removedEntries.Add(input[i + offset]); offset++; Console.WriteLine("i="+i+", offset="+offset); } if(i < input.Count - offset) { input[i] = input[i+offset]; } } input.RemoveRange(input.Count - offset, offset); return removedEntries; }Recorremos la lista y comprobamos si un elemento coincide con la condición. Si la condición coincide, el elemento posterior a ese elemento se copia en la posición. Entonces, todos los elementos que no cumplan la condición estarán al principio de la lista, y todos los elementos que cumplan la condición estarán al final de la lista. En el último paso, se eliminan los elementos al final de la lista.
Puede ser conveniente dar una capacidad inicial a la lista de removedEntries . Por defecto, una lista tiene una capacidad de 4, que se duplica cada vez que se supera. Si tiene 100 elementos para eliminar, la capacidad debe ampliarse 5 veces. Esta es una operación O(n) cada vez. Si puede estimar que eliminará alrededor del 10% de los elementos, podría escribir
List<T> removedEntries = new List<T>(input.Count / 10);Esto podría ahorrarle algo de tiempo, pero por otro lado, si no necesita la capacidad inicial completa de la lista, desperdiciará algo de memoria.
Demostración en línea: https://dotnetfiddle.net/dlthkH
Una mejora de la eficiencia (aunque en última instancia sigue siendo O(n)) es agrupar las eliminaciones por lotes. Mis pruebas muestran que, dependiendo de la frecuencia de eliminación, esta puede ser la misma velocidad o más de 4 veces más rápida. Aquí está la función como un método de extensión:
public static List<T> RemoveAllAndReturn<T>(this List<T> input, Func<T, bool> condition) { List<T> result = new List<T>(); var removeCt = 0; for (int i = input.Count - 1; i >= 0; --i) { if (condition(input[i])) { result.Add(input[i]); ++removeCt; } else if (removeCt > 0) { input.RemoveRange(i + 1, removeCt); removeCt = 0; } } if (removeCt > 0) input.RemoveRange(0, removeCt); return result; }Podrías considerar hacer este truco:
var subList = new List<SomeType>(); originalList.RemoveAll(x => { bool shouldBeRemoved = x.Number < 0; if (shouldBeRemoved) subList.Add(x); return shouldBeRemoved; }); El Predicate<T> pasado a RemoveAll no es puro: tiene el efecto secundario de insertar elementos coincidentes en la subList . Basado en la implementación del método RemoveAll , este truco debería funcionar como se esperaba. Sin embargo, la documentación no garantiza explícitamente que el predicado se invocará solo una vez por elemento:
Los elementos de
List<T>actual se pasan individualmente al delegado dePredicate<T>y los elementos que coinciden con las condiciones se quitan deList<T>.
Así que haz tu propio juicio sobre si es seguro usar este truco o no.
Editar: también podría convertirlo en un método de extensión:
public static int RemoveAll<T>(this List<T> source, Predicate<T> match, out List<T> removed) { var removedLocal = new List<T>(); removed = removedLocal; int removedCount = source.RemoveAll(x => { bool shouldBeRemoved = match(x); if (shouldBeRemoved) removedLocal.Add(x); return shouldBeRemoved; }); Debug.Assert(removedCount == removed.Count); return removedCount; }Ejemplo de uso:
originalList.RemoveAll(x => x.number < 0, out var subList);