Digamos que tengo una secuencia.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Obtener la secuencia no es barata y se genera dinámicamente, y quiero repetirla solo una vez.
Quiero obtener 0 - 999999 (es decir, todo menos el último elemento)
Reconozco que podría hacer algo como:
sequence.Take(sequence.Count() - 1);
pero eso da como resultado dos enumeraciones sobre la secuencia grande.
¿Hay una construcción LINQ que me permite hacer:
sequence.TakeAllButTheLastElement();
sequenceList.RemoveAt(sequence.Count - 1);
. En mi caso es aceptable porque después de todas las manipulaciones de LINQ tengo que convertirlo a una matriz o deIReadOnlyCollection
todos modos. Me pregunto cuál es su caso de uso en el que ni siquiera considera el almacenamiento en caché. Como puedo ver, incluso la respuesta aprobada hace un almacenamiento en caché, por lo que la conversión simpleList
es mucho más fácil y más corta en mi opinión.Respuestas:
No conozco una solución de Linq, pero puede codificar fácilmente el algoritmo usted mismo utilizando generadores (rendimiento de rendimiento).
O como una solución generalizada descartando los últimos n elementos (usando una cola como se sugiere en los comentarios):
fuente
Como alternativa a la creación de su propio método y, en un caso, el orden de los elementos no es importante, el siguiente funcionará:
fuente
equence.Reverse().Skip(1).Reverse()
no es una buena soluciónComo no soy fanático de usar explícitamente una
Enumerator
, aquí hay una alternativa. Tenga en cuenta que los métodos de envoltura son necesarios para permitir que los argumentos no válidos se lancen temprano, en lugar de diferir las comprobaciones hasta que la secuencia se enumere realmente.Según la sugerencia de Eric Lippert, se generaliza fácilmente a n elementos:
Donde ahora amortiguo antes de ceder en lugar de después de ceder, para que el
n == 0
caso no necesite un manejo especial.fuente
buffered=false
una cláusula else antes de asignarbuffer
. La condición ya se está verificando de todos modos, pero esto evitaría configurar de forma redundantebuffered
cada vez a través del bucle.DropLast
. De lo contrario, la validación ocurre solo cuando realmente enumera la secuencia (en la primera llamada alMoveNext
resultadoIEnumerator
). No es una cosa divertida de depurar cuando podría haber una cantidad arbitraria de código entre generarloIEnumerable
y realmente enumerarlo. Hoy en día escribiríaInternalDropLast
como una función interna deDropLast
, pero esa funcionalidad no existía en C # cuando escribí este código hace 9 años.El
Enumerable.SkipLast(IEnumerable<TSource>, Int32)
método fue agregado en .NET Standard 2.1. Hace exactamente lo que quieres.Desde https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
fuente
No hay nada en el BCL (o en MoreLinq, creo), pero podría crear su propio método de extensión.
fuente
if (!first)
y sacarlofirst = false
del if.prev
en la primera iteración y hacer un ciclo para siempre después de eso'.using
declaración ahora.Sería útil que .NET Framework se enviara con un método de extensión como este.
fuente
Una ligera expansión en la elegante solución de Joren:
Donde shrink implementa un conteo simple hacia adelante para descartar los primeros
left
elementos y el mismo búfer descartado para descartar los últimosright
elementos.fuente
Si no tiene tiempo para implementar su propia extensión, esta es una forma más rápida:
fuente
Una ligera variación en la respuesta aceptada, que (para mis gustos) es un poco más simple:
fuente
Si puede obtener el
Count
oLength
de un enumerable, que en la mayoría de los casos puede, entonces simplementeTake(n - 1)
Ejemplo con matrices
Ejemplo con
IEnumerable<T>
fuente
¿Por qué no solo
.ToList<type>()
en la secuencia, luego llame y cuente como lo hizo originalmente ... pero como se ha incluido en una lista, no debería hacer una enumeración costosa dos veces? ¿Correcto?fuente
La solución que uso para este problema es un poco más elaborada.
Mi clase estática util contiene un método de extensión
MarkEnd
que convierte losT
-items enEndMarkedItem<T>
-items. Cada elemento está marcado con un extraint
, que es 0 ; o (en caso de que uno esté particularmente interesado en los últimos 3 elementos) -3 , -2 o -1 para los últimos 3 elementos.Esto podría ser útil por sí solo, por ejemplo , cuando desee crear una lista en un
foreach
bucle simple con comas después de cada elemento, excepto los 2 últimos, con el penúltimo elemento seguido de una palabra de conjunción (como " y " o " o "), y el último elemento seguido de un punto.Para generar la lista completa sin los últimos n elementos, el método de extensión
ButLast
simplemente itera sobre elEndMarkedItem<T>
s whileEndMark == 0
.Si no especifica
tailLength
, solo el último elemento se marca (enMarkEnd()
) o se suelta (enButLast()
).Al igual que las otras soluciones, esto funciona mediante almacenamiento en búfer.
fuente
fuente
No creo que pueda ser más sucinto que esto, y también me aseguro de desechar
IEnumerator<T>
:Editar: técnicamente idéntico a esta respuesta .
fuente
Con C # 8.0 puede usar rangos e índices para eso.
De manera predeterminada, C # 8.0 requiere .NET Core 3.0 o .NET Standard 2.1 (o superior). Verifique este hilo para usar con implementaciones anteriores.
fuente
Podrías escribir:
fuente
Esta es una solución elegante general y en mi humilde opinión que manejará todos los casos correctamente:
fuente
Mi
IEnumerable
enfoque tradicional :fuente
Una forma simple sería simplemente convertir a una cola y retirar hasta que solo quede la cantidad de elementos que desea omitir.
fuente
Podría ser:
Supongo que debería ser como "Dónde" pero preservando el orden (?).
fuente
Si la velocidad es un requisito, este método de la vieja escuela debería ser el más rápido, a pesar de que el código no se ve tan fluido como lo podría hacer linq.
Esto requiere que la secuencia sea una matriz, ya que tiene una longitud fija y elementos indexados.
fuente
Probablemente haría algo como esto:
Sin embargo, esta es una iteración con una verificación de que no es la última.
fuente