Dada una colección, ¿hay alguna forma de obtener los últimos N elementos de esa colección? Si no hay un método en el marco, ¿cuál sería la mejor manera de escribir un método de extensión para hacer esto?
collection.Skip(Math.Max(0, collection.Count() - N));
Este enfoque conserva el orden de los artículos sin depender de ninguna clasificación y tiene una amplia compatibilidad entre varios proveedores de LINQ.
Es importante tener cuidado de no llamar Skip
con un número negativo. Algunos proveedores, como el Entity Framework, producirán una excepción ArgumentException cuando se les presente un argumento negativo. La llamada aMath.Max
evita esto perfectamente.
La siguiente clase tiene todos los elementos esenciales para los métodos de extensión, que son: una clase estática, un método estático y el uso de la this
palabra clave.
public static class MiscExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
}
Una breve nota sobre el rendimiento:
Debido a que la llamada a Count()
puede causar la enumeración de ciertas estructuras de datos, este enfoque tiene el riesgo de causar dos pases sobre los datos. Esto no es realmente un problema con la mayoría de los enumerables; de hecho, ya existen optimizaciones para Listas, Arreglos e incluso consultas EF para evaluar la Count()
operación en tiempo O (1).
Sin embargo, si debe usar un enumerable solo hacia adelante y desea evitar hacer dos pases, considere un algoritmo de un solo paso como describen Lasse V. Karlsen o Mark Byers . Ambos enfoques utilizan un búfer temporal para contener elementos durante la enumeración, que se obtienen una vez que se encuentra el final de la colección.
List
syLinkedList
s, la solución de James tiende a ser más rápida, aunque no por un orden de magnitud. Si se calcula IEnumerable (a través de Enumerable.Range, por ejemplo), la solución de James lleva más tiempo. No se me ocurre ninguna forma de garantizar una sola pasada sin saber algo sobre la implementación o copiar valores en una estructura de datos diferente.ACTUALIZACIÓN: Para abordar el problema de clintp: a) El uso del método TakeLast () que definí anteriormente resuelve el problema, pero si realmente quieres hacerlo sin el método adicional, entonces solo tienes que reconocerlo mientras Enumerable.Reverse () puede ser utilizado como método de extensión, no es necesario que lo use de esa manera:
fuente
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
me sale un error del compilador porque .Reverse () devuelve void y el compilador elige ese método en lugar del método Linq que devuelve un IEnumerable. Sugerencias?N
registros, puede omitir el segundoReverse
.Nota : Me perdí el título de su pregunta que decía Usar Linq , por lo que mi respuesta no usa Linq.
Si desea evitar el almacenamiento en caché de una copia no perezosa de toda la colección, puede escribir un método simple que lo haga utilizando una lista vinculada.
El siguiente método agregará cada valor que encuentre en la colección original en una lista vinculada y recortará la lista vinculada a la cantidad de elementos necesarios. Como mantiene la lista vinculada ajustada a este número de elementos todo el tiempo a través de la iteración a través de la colección, solo mantendrá una copia de como máximo N elementos de la colección original.
No requiere que sepa la cantidad de elementos en la colección original, ni que repita más de una vez.
Uso:
Método de extensión:
fuente
Aquí hay un método que funciona en cualquier enumerable pero usa solo almacenamiento temporal O (N):
Uso:
Funciona mediante el uso de un búfer de anillo de tamaño N para almacenar los elementos tal como los ve, sobrescribiendo los elementos antiguos con los nuevos. Cuando se alcanza el final de lo enumerable, el buffer de anillo contiene los últimos N elementos.
fuente
n
..NET Core 2.0+ proporciona el método LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
ejemplo :
fuente
netcoreapp1.x
) sino solo para v2.0 y v2.1 de dotnetcore (netcoreapp2.x
). Es posible que tenga como objetivo el marco completo (pnet472
. Ej. ) Que tampoco es compatible. (Las bibliotecas estándar de .net pueden ser utilizadas por cualquiera de los anteriores, pero solo pueden exponer ciertas API específicas a un marco de destino. ver docs.microsoft.com/en-us/dotnet/standard/frameworks )Me sorprende que nadie lo haya mencionado, pero SkipWhile sí tiene un método que utiliza el índice del elemento .
El único beneficio perceptible que esta solución presenta sobre otros es que puede tener la opción de agregar un predicado para hacer una consulta LINQ más potente y eficiente, en lugar de tener dos operaciones separadas que atraviesen el IEnumerable dos veces.
fuente
Use EnumerableEx.TakeLast en el sistema de RX. Ensamblaje interactivo. Es una implementación O (N) como @ Mark's, pero utiliza una cola en lugar de una construcción de anillo de búfer (y retira los elementos cuando alcanza la capacidad de búfer).
(Nota: esta es la versión IEnumerable, no la versión IObservable, aunque la implementación de las dos es bastante idéntica)
fuente
Queue<T>
implementa C # usando un búfer circular ?Si se trata de una colección con una clave (por ejemplo, entradas de una base de datos), una solución rápida (es decir, más rápida que la respuesta seleccionada) sería
fuente
Si no te importa sumergirte en Rx como parte de la mónada, puedes usar
TakeLast
:fuente
Si usar una biblioteca de terceros es una opción, MoreLinq define
TakeLast()
qué hace exactamente esto.fuente
Intenté combinar eficiencia y simplicidad y terminar con esto:
Acerca del rendimiento: en C #,
Queue<T>
se implementa utilizando un búfer circular para que no se ejecute ninguna instancia de objeto en cada bucle (solo cuando la cola está creciendo). No configuré la capacidad de la cola (usando un constructor dedicado) porque alguien podría llamar a esta extensión concount = int.MaxValue
. Para obtener un rendimiento adicional, puede verificar si la fuente se implementaIList<T>
y, en caso afirmativo, extraer directamente los últimos valores utilizando índices de matriz.fuente
Es un poco ineficiente tomar el último N de una colección usando LINQ ya que todas las soluciones anteriores requieren iterar en toda la colección.
TakeLast(int n)
enSystem.Interactive
también tiene este problema.Si tiene una lista, lo más eficiente es cortarla con el siguiente método
con
y algunos casos de prueba
fuente
Sé que es demasiado tarde para responder esta pregunta. Pero si está trabajando con una colección de tipo IList <> y no le importa un orden de la colección devuelta, entonces este método funciona más rápido. Utilicé la respuesta de Mark Byers e hice algunos pequeños cambios. Entonces, el método TakeLast es:
Para la prueba he usado el método Mark Byers y kbrimington's andswer . Esta es la prueba:
Y aquí están los resultados para tomar 10 elementos:
y para tomar 1000001 elementos los resultados son:
fuente
Aquí está mi solución:
El código es un poco grueso, pero como componente reutilizable, debe funcionar tan bien como puede en la mayoría de los escenarios, y mantendrá el código que lo está usando de manera agradable y concisa. :-)
Mi
TakeLast
para noIList`1
se basa en el mismo algoritmo de buffer de anillo que el de las respuestas de @Mark Byers y @MackieChan más arriba. Es interesante lo similares que son: escribí el mío de forma completamente independiente. Supongo que en realidad solo hay una manera de hacer un buffer de anillo correctamente. :-)Mirando la respuesta de @kbrimington, se podría agregar una verificación adicional para
IQuerable<T>
volver al enfoque que funciona bien con Entity Framework, suponiendo que lo que tengo en este momento no.fuente
Debajo del ejemplo real de cómo tomar los últimos 3 elementos de una colección (matriz):
fuente
Usando este método para obtener todo el rango sin error
fuente
Implementación poco diferente con el uso de búfer circular. Los puntos de referencia muestran que el método es aproximadamente dos veces más rápido que los que usan Queue (implementación de TakeLast en System.Linq ), pero no sin costo: necesita un búfer que crece junto con el número de elementos solicitados, incluso si tiene un pequeña colección puede obtener una gran asignación de memoria.
fuente