Cómo tomar todos pero el último elemento en una secuencia utilizando LINQ?

Digamos que tengo una secuencia.

IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
//sequence now contains: 0,1,2,3,...,999999,1000000

Obtención de la secuencia no es barato, y se genera de forma dinámica, y quiero repetir una vez sólo.

Quiero llegar a 0 – 999999 (es decir, de todo, pero el último elemento)

Reconozco que yo podría hacer algo como:

sequence.Take(sequence.Count() - 1);

pero que los resultados en dos enumeraciones más de la gran secuencia.

Hay una LINQ constructo que me permite hacer:

sequence.TakeAllButTheLastElement();
  • Has de elegir entre una O(2n) en tiempo o(recuento) de espacio en la eficiencia del algoritmo, donde el último también se necesita para mover los elementos en una matriz interna.
  • Darío, por favor, ¿puedes explicar para alguien que no que en gran notación o?
  • Ver también esta pregunta duplicada: stackoverflow.com/q/4166493/240733
  • Terminé con el almacenamiento en caché mediante la conversión de la colección a la Lista y, a continuación, llamar a sequenceList.RemoveAt(sequence.Count - 1);. En mi caso es aceptable porque después de todo LINQ manipulaciones tengo que convertir a la matriz o IReadOnlyCollection de todos modos. Me pregunto cuál es su uso caso de que usted incluso no considerar el almacenamiento en caché? Como puedo ver incluso aprobado respuesta a algunas de almacenamiento en caché, así de simple conversión a List es mucho más fácil y más corto en mi opinión.
InformationsquelleAutor Mike | 2009-11-22

22 Kommentare

  1. 58

    No sé Linq solución – Pero usted puede fácilmente el código del algoritmo por sí mismo el uso de generadores (rendimiento).

    public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source) {
        var it = source.GetEnumerator();
        bool hasRemainingItems = false;
        bool isFirst = true;
        T item = default(T);
    
        do {
            hasRemainingItems = it.MoveNext();
            if (hasRemainingItems) {
                if (!isFirst) yield return item;
                item = it.Current;
                isFirst = false;
            }
        } while (hasRemainingItems);
    }
    
    static void Main(string[] args) {
        var Seq = Enumerable.Range(1, 10);
    
        Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
        Console.WriteLine(string.Join(", ", Seq.TakeAllButLast().Select(x => x.ToString()).ToArray()));
    }

    O como una generalización de la solución descartando los últimos elementos de n (usando una cola, como se sugiere en los comentarios):

    public static IEnumerable<T> SkipLastN<T>(this IEnumerable<T> source, int n) {
        var  it = source.GetEnumerator();
        bool hasRemainingItems = false;
        var  cache = new Queue<T>(n + 1);
    
        do {
            if (hasRemainingItems = it.MoveNext()) {
                cache.Enqueue(it.Current);
                if (cache.Count > n)
                    yield return cache.Dequeue();
            }
        } while (hasRemainingItems);
    }
    
    static void Main(string[] args) {
        var Seq = Enumerable.Range(1, 4);
    
        Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray()));
        Console.WriteLine(string.Join(", ", Seq.SkipLastN(3).Select(x => x.ToString()).ToArray()));
    }
    • Ahora se puede generalizar a tomar a todos en la final, pero n?
    • De niza. Un pequeño error; el tamaño de la cola debe ser inicializado para n + 1, ya que es el tamaño máximo de la cola.
    • ReSharper no entiende su código (de la asignación en la expresión condicional), pero me gusta un poco +1
    • Tenga en cuenta que IEnumerator<T> es IDisposable.
  2. 43

    Como una alternativa a la creación de su propio método y en un caso, los elementos de orden no es importante, la siguiente obra:

    var result = sequence.Reverse().Skip(1);
    • Tenga en cuenta que esto requiere que usted tenga la memoria suficiente para almacenar toda la secuencia, y por supuesto AÚN recorre toda la secuencia dos veces, una vez para construir la inversión de la secuencia y de una vez para recorrer la misma. Prácticamente esto es peor que el Recuento de solución no importa que la veas; es más lento Y tarda mucho más de memoria.
    • No sé cómo la «Inversa» método de trabajo, así que no estoy seguro acerca de la cantidad de memoria que se utilice. Pero estoy de acuerdo acerca de dos iteraciones. Este método no debe ser utilizado en grandes colecciones o si el rendimiento es importante.
    • Bien, ¿cómo sería implementar la Inversa? Puede usted encontrar una manera general sin necesidad de almacenar toda la secuencia?
    • No veo nada mejor que O(n) de espacio. Asegúrese de que la mejor solución de uso de método personalizado para este propósito.
    • Me gusta, y no cumple con los criterios de la no generación de la secuencia dos veces.
    • y, además, usted también tendrá que invertir la secuencia entera de nuevo para guardarlo como es equence.Reverse().Skip(1).Reverse() no es una buena solución

  3. 42

    Porque yo no soy un fan de forma explícita el uso de un Enumerator, aquí tienes una alternativa. Tenga en cuenta que el contenedor métodos son necesarios para permitir que los argumentos inválidos tirar temprano, en lugar de aplazar los cheques hasta que la secuencia es en realidad enumerados.

    public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");
    
        return InternalDropLast(source);
    }
    
    private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source)
    {
        T buffer = default(T);
        bool buffered = false;
    
        foreach (T x in source)
        {
            if (buffered)
                yield return buffer;
    
            buffer = x;
            buffered = true;
        }
    }

    Como por Eric Lippert sugerencia, se generaliza fácilmente a los elementos de n:

    public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n)
    {
        if (source == null)
            throw new ArgumentNullException("source");
    
        if (n < 0)
            throw new ArgumentOutOfRangeException("n", 
                "Argument n should be non-negative.");
    
        return InternalDropLast(source, n);
    }
    
    private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n)
    {
        Queue<T> buffer = new Queue<T>(n + 1);
    
        foreach (T x in source)
        {
            buffer.Enqueue(x);
    
            if (buffer.Count == n + 1)
                yield return buffer.Dequeue();
        }
    }

    Donde yo ahora búfer antes de produciendo en lugar de después de ceder, de modo que el n == 0 caso de que no necesitan un tratamiento especial.

    • En el primer ejemplo, probablemente sería siempre tan ligeramente más rápido para establecer buffered=false en una cláusula else antes de asignar buffer. La condición es que ya se comprueban todos modos, pero esto podría evitar de forma redundante configuración buffered cada vez a través del bucle.
    • Alguien puede decirme los pros y los contras de este frente a la respuesta seleccionada?
    • ¿Cuál es la ventaja de tener la aplicación de un método independiente de que carece de la entrada de los cheques? También, que acababa de soltar la sola implementación y dar las múltiples implementación de un valor predeterminado.
    • Con el cheque en un método independiente, en realidad obtiene la validación en el momento que usted llame DropLast. De lo contrario, la validación sólo ocurre cuando en realidad enumerar la secuencia (en la primera llamada a MoveNext en la resultante IEnumerator). No es una cosa de la diversión para depurar cuando podría haber una cantidad arbitraria de código entre la generación de la IEnumerable y de hecho la enumeración de ella. Hoy me gustaría escribir InternalDropLast interior de la función de DropLast, sino que la funcionalidad no existe en C# cuando escribí este código hace 9 años.
  4. 12

    Nada en la BCL (o MoreLinq creo), pero usted puede crear su propio método de extensión.

    public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source)
    {
        using (var enumerator = source.GetEnumerator())
            bool first = true;
            T prev;
            while(enumerator.MoveNext())
            {
                if (!first)
                    yield return prev;
                first = false;
                prev = enumerator.Current;
            }
        }
    }
    • Este código no funciona… probablemente debería ser if (!first) y tire de first = false de la si.
    • Este código se ve fuera. La lógica parece ser «devolver el no inicializada prev en la primera iteración, y bucle para siempre después de que’.
    • El código parece tener «tiempo de compilación» de error. Puede ser que te gustaría corregir. Pero sí, podemos escribir un extensor que se repite una vez y regresa a todos, pero el último elemento.
    • Tienes toda la razón – escribí esto en una carrera real! Fijo ahora. @Amby: Erm, no estoy seguro qué compilador que se está usando. Yo no tenía nada de eso. 😛
    • Tenga en cuenta que IEnumerator<T> es IDisposable.
    • Uy, sí. He añadido un using declaración de ahora.

  5. 7

    Sería de gran ayuda si .NET Framework fue enviado con el método de extensión como esta.

    public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
    {
        var enumerator = source.GetEnumerator();
        var queue = new Queue<T>(count + 1);
    
        while (true)
        {
            if (!enumerator.MoveNext())
                break;
            queue.Enqueue(enumerator.Current);
            if (queue.Count > count)
                yield return queue.Dequeue();
        }
    }
    • Tenga en cuenta que IEnumerator<T> es IDisposable.
  6. 4

    si usted no tiene tiempo para desplegar su propia extensión, aquí una forma más rápida:

    var next = sequence.First();
    sequence.Skip(1)
        .Select(s => 
        { 
            var selected = next;
            next = s;
            return selected;
        });
  7. 3

    Una ligera expansión en Joren la elegante solución:

    public static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right)
    {
        int i = 0;
        var buffer = new Queue<T>(right + 1);
    
        foreach (T x in source)
        {
            if (i >= left) //Read past left many elements at the start
            {
                buffer.Enqueue(x);
                if (buffer.Count > right) //Build a buffer to drop right many elements at the end
                    yield return buffer.Dequeue();    
            } 
            else i++;
        }
    }
    public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1)
    {
        return source.Shrink(0, n);
    }
    public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1)
    {
        return source.Shrink(n, 0);
    }

    Donde reducir implementa un simple recuento hacia adelante para soltar la primera left muchos elementos y la misma descarta búfer a la caída de la última right muchos elementos.

  8. 3

    Si usted puede conseguir el Count o Length de un enumerable, que en la mayoría de los casos usted puede, a continuación, sólo Take(n - 1)

    Ejemplo con matrices

    int[] arr = new int[] { 1, 2, 3, 4, 5 };
    int[] sub = arr.Take(arr.Length - 1).ToArray();

    Ejemplo con IEnumerable<T>

    IEnumerable<int> enu = Enumerable.Range(1, 100);
    IEnumerable<int> sub = enu.Take(enu.Count() - 1);
    • La pregunta es acerca de IEnumerables y su solución es lo OP que está tratando de evitar. El código tiene impacto en el rendimiento.
  9. 2

    Una ligera variación en la aceptación de respuesta, que (para mi gusto) es un poco más simple:

        public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
        {
            //for efficiency, handle degenerate n == 0 case separately 
            if (n == 0)
            {
                foreach (var item in enumerable)
                    yield return item;
                yield break;
            }
    
            var queue = new Queue<T>(n);
            foreach (var item in enumerable)
            {
                if (queue.Count == n)
                    yield return queue.Dequeue();
    
                queue.Enqueue(item);
            }
        }
  10. 1

    ¿Por qué no .ToList<type>() en la secuencia, luego recuento de llamadas y tomar como lo hizo originalmente..pero desde que se detuvo en una lista, no debería hacer un costoso enumeración dos veces. A la derecha?

  11. 1

    La solución que yo uso para este problema es un poco más elaborado.

    Mi util estático de la clase contiene un método de extensión MarkEnd que convierte el T-elementos en EndMarkedItem<T>-elementos. Cada elemento está marcado con un extra de int, que es 0; o (en caso de que uno está particularmente interesado en los últimos 3 elementos) -3, -2, o -1 durante los últimos 3 elementos.

    Esto podría ser útil en sí mismo, por ejemplo, cuando usted desea crear una lista en un simple foreach-bucle con comas después de cada elemento, excepto los 2 últimos, con el segundo al último elemento seguido por una conjunción de palabras (como «y» o «o«), y el último elemento seguido por un punto.

    Para la generación de toda la lista sin el último n elementos, el método de extensión ButLast simplemente recorre el EndMarkedItem<T>s mientras EndMark == 0.

    Si no se especifica tailLength, sólo el último elemento está marcado (en MarkEnd()) o disminuido (en ButLast()).

    Igual que el de otras soluciones, esto funciona mediante el almacenamiento en búfer.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace Adhemar.Util.Linq {
    
        public struct EndMarkedItem<T> {
            public T Item { get; private set; }
            public int EndMark { get; private set; }
    
            public EndMarkedItem(T item, int endMark) : this() {
                Item = item;
                EndMark = endMark;
            }
        }
    
        public static class TailEnumerables {
    
            public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts) {
                return ts.ButLast(1);
            }
    
            public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts, int tailLength) {
                return ts.MarkEnd(tailLength).TakeWhile(te => te.EndMark == 0).Select(te => te.Item);
            }
    
            public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts) {
                return ts.MarkEnd(1);
            }
    
            public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts, int tailLength) {
                if (tailLength < 0) {
                    throw new ArgumentOutOfRangeException("tailLength");
                }
                else if (tailLength == 0) {
                    foreach (var t in ts) {
                        yield return new EndMarkedItem<T>(t, 0);
                    }
                }
                else {
                    var buffer = new T[tailLength];
                    var index = -buffer.Length;
                    foreach (var t in ts) {
                        if (index < 0) {
                            buffer[buffer.Length + index] = t;
                            index++;
                        }
                        else {
                            yield return new EndMarkedItem<T>(buffer[index], 0);
                            buffer[index] = t;
                            index++;
                            if (index == buffer.Length) {
                                index = 0;
                            }
                        }
                    }
                    if (index >= 0) {
                        for (var i = index; i < buffer.Length; i++) {
                            yield return new EndMarkedItem<T>(buffer[i], i - buffer.Length - index);
                        }
                        for (var j = 0; j < index; j++) {
                            yield return new EndMarkedItem<T>(buffer[j], j - index);
                        }
                    }
                    else {
                        for (var k = 0; k < buffer.Length + index; k++) {
                            yield return new EndMarkedItem<T>(buffer[k], k - buffer.Length - index);
                        }
                    }
                }    
            }
        }
    }
  12. 1
        public static IEnumerable<T> NoLast<T> (this IEnumerable<T> items) {
            if (items != null) {
                var e = items.GetEnumerator();
                if (e.MoveNext ()) {
                    T head = e.Current;
                    while (e.MoveNext ()) {
                        yield return head; ;
                        head = e.Current;
                    }
                }
            }
        }
  13. 1

    No creo que se puede conseguir más sucinta que esto – además de garantizar a Disponer la IEnumerator<T>:

    public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
    {
        using (var it = source.GetEnumerator())
        {
            if (it.MoveNext())
            {
                var item = it.Current;
                while (it.MoveNext())
                {
                    yield return item;
                    item = it.Current;
                }
            }
        }
    }

    Edición: técnicamente idéntica a esta respuesta.

  14. 0

    Usted podría escribir:

    var list = xyz.Select(x=>x.Id).ToList();
    list.RemoveAt(list.Count - 1);
  15. 0

    Esta es una general y en mi humilde opinión solución elegante que se encargará de todos los casos correctamente:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public class Program
    {
        public static void Main()
        {
            IEnumerable<int> r = Enumerable.Range(1, 20);
            foreach (int i in r.AllButLast(3))
                Console.WriteLine(i);
    
            Console.ReadKey();
        }
    }
    
    public static class LinqExt
    {
        public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1)
        {
            using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
            {
                Queue<T> queue = new Queue<T>(n);
    
                for (int i = 0; i < n && enumerator.MoveNext(); i++)
                    queue.Enqueue(enumerator.Current);
    
                while (enumerator.MoveNext())
                {
                    queue.Enqueue(enumerator.Current);
                    yield return queue.Dequeue();
                }
            }
        }
    }
    • Tenga en cuenta que IEnumerator<T> es IDisposable.
  16. -1

    Mi tradicionales IEnumerable enfoque:

    ///<summary>
    ///Skips first element of an IEnumerable
    ///</summary>
    ///<typeparam name="U">Enumerable type</typeparam>
    ///<param name="models">The enumerable</param>
    ///<returns>IEnumerable of type skipping first element</returns>
    private IEnumerable<U> SkipFirstEnumerable<U>(IEnumerable<U> models)
    {
        using (var e = models.GetEnumerator())
        {
            if (!e.MoveNext()) return;
            for (;e.MoveNext();) yield return e.Current;
            yield return e.Current;
        }
    }
    
    ///<summary>
    ///Skips last element of an IEnumerable
    ///</summary>
    ///<typeparam name="U">Enumerable type</typeparam>
    ///<param name="models">The enumerable</param>
    ///<returns>IEnumerable of type skipping last element</returns>
    private IEnumerable<U> SkipLastEnumerable<U>(IEnumerable<U> models)
    {
        using (var e = models.GetEnumerator())
        {
            if (!e.MoveNext()) return;
            yield return e.Current;
            for (;e.MoveNext();) yield return e.Current;
        }
    }
    • Su SkipLastEnumerable puede ser tradicional, pero está roto. El primer elemento que se devuelve es siempre un indefinido U – incluso cuando los «modelos» tiene 1 elemento. En el último caso, me gustaría esperar un resultado vacío.
    • También, IEnumerator<T> es IDisposable.
    • Verdadero/señaló. Gracias.
  17. -1

    Una forma sencilla sería la de convertir a una cola y la cola hasta que sólo el número de elementos que desea saltar es a la izquierda.

    public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n)
    {
        var queue = new Queue<T>(source);
    
        while (queue.Count() > n)
        {
            yield return queue.Dequeue();
        }
    }
    • Hay Toma se utiliza para tomar conoce el número de elementos. Y la cola bastante grande enumerable es horrible.
  18. -2

    Podría ser:

    var allBuLast = sequence.TakeWhile(e => e != sequence.Last());

    Supongo que debe ser como la de «Donde», pero conservando el orden(?).

    • Esa es una manera muy ineficiente para hacerlo. Para evaluar la secuencia.Last() se tiene que atravesar toda la secuencia, haciendo así para cada elemento de la secuencia. O(n^2) eficiencia.
    • Estás en lo correcto. Yo no sé en qué estaba pensando cuando escribí esto XD. De todos modos, está seguro de que el Último() va a atravesar toda la secuencia? Para algunas implementaciones de IEnumerable (como la de la Matriz), este debe ser O(1). Yo no comprobar la Lista de la aplicación, sino que también podría tener una «marcha atrás» iterador, comenzando en el último elemento, el cual llevaría a O(1), también. Además, usted debe tomar en cuenta que S(n) = O(2n), al menos técnicamente hablando. Por lo tanto, si este procedimiento no es absolutamente crítico para su rendimiento de aplicaciones, yo me quedaría con la idea mucho más clara de la secuencia.Tomar(la secuencia.Count() – 1).Saludos!
    • No estoy de acuerdo con usted compañero, de la secuencia.Last() es O(1), de modo que no es necesario recorrer toda la secuencia. stackoverflow.com/a/1377895/812598
    • solo es de O(1) si la secuencia implementa ICollection/IList o es una Matriz. Todas las otras secuencias son O(N). En mi pregunta, yo no doy por sentado que uno de los O(1) fuentes de
    • Bueno, sí, tienes razón, dependiendo de lo que implementar en la secuencia. Sin embargo parece que no es O(1) para ICollection/IList como usted ha dicho, es sólo O(1) para IList sólo stackoverflow.com/a/18200099/812598
    • La secuencia puede tener varios elementos que satisface esta condición e == secuencia.Last(), por ejemplo, nuevas [] { 1, 1, 1, 1 }

  19. -2

    Si la velocidad es un requisito, esta vieja escuela debería ser el más rápido, aunque el código no es tan suave como linq podría hacer.

    int[] newSequence = int[sequence.Length - 1];
    for (int x = 0; x < sequence.Length - 1; x++)
    {
        newSequence[x] = sequence[x];
    }

    Esto requiere que la secuencia es una matriz, ya que tiene una longitud fija y elementos indizados.

    • Se trata de un IEnumerable que no permiten el acceso a los elementos a través de un índice. Su solución no funciona. Suponiendo que hacerlo bien, requiere atravesar la secuencia para determinar la longitud, la asignación de una matriz de longitud n-1, copia de todos los elementos. – 1. 2n-1 operaciones y (2n-1) * (4 o 8 bytes) de memoria. Eso no es ni rápido.
  20. -4

    Probablemente voy a hacer algo como esto:

    sequence.Where(x => x != sequence.LastOrDefault())

    Esta es una iteración con un cheque que no es la última para cada tiempo, sin embargo.

    • Dos razones por las que no es una buena cosa que hacer. 1) .LastOrDefault() requiere recorrer toda la secuencia, y eso se llama para cada elemento de la secuencia (en el .Donde()). 2) Si la secuencia es [1,2,1,2,1,2] y utiliza su técnica, que estaría a la izquierda con [1,1,1].

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea