visual studio secuencia mas elementos elemento contiene coincidente c# .net linq

c# - studio - ¿Cómo tomar todo excepto el último elemento en una secuencia usando LINQ?



la secuencia no contiene elementos visual studio (19)

Digamos que tengo una secuencia.

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

Obtener la secuencia no es barato y se genera dinámicamente, y quiero iterar a través de ella 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 resulta en dos enumeraciones sobre la secuencia grande.

¿Hay algún constructo de LINQ que me permita:

sequence.TakeAllButTheLastElement();


¿Por qué no simplemente .ToList<type>() en la secuencia, luego conteo de llamadas y tomar como lo hizo originalmente ... pero como se ha incluido en una lista, no debería hacer una enumeración costosa dos veces. ¿Derecha?


Como alternativa a crear su propio método y, en un caso, el orden de los elementos no es importante, el siguiente funcionará:

var result = sequence.Reverse().Skip(1);


Como no soy partidario de usar explícitamente un Enumerator , aquí hay una alternativa. Tenga en cuenta que los métodos de contenedor son necesarios para permitir que los argumentos no válidos se envíen antes, en lugar de diferir los controles hasta que la secuencia se enumere realmente.

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; } }

Según la sugerencia de Eric Lippert, se generaliza fácilmente a n elementos:

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 ahora guardo el buffer antes de ceder en lugar de después de ceder, de modo que el caso n == 0 no necesita un manejo especial.


Esta es una solución general y en mi humilde opinión que manejará 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(); } } } }


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 los T elementos en elementos EndMarkedItem<T> . Cada elemento está marcado con un int extra, 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 artículos.

Esto podría ser útil por sí mismo, por ejemplo , cuando desee crear una lista en un simple foreach loop con comas después de cada elemento, excepto los últimos 2, 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 EndMarkedItem<T> s mientras EndMark == 0 .

Si no especifica tailLength , solo se marcará el último elemento (en MarkEnd() ) o se descartará (en ButLast() ).

Al igual que las 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); } } } } } }


Mi enfoque tradicional de IEnumerable :

/// <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; } }


Nada en el BCL (o MoreLinq, creo), pero podría 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; } } }


No conozco una solución Linq. Pero puede codificar fácilmente el algoritmo usted mismo usando generadores (retorno de 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 solución generalizada descartando los últimos n elementos (utilizando una cola como la sugerida 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())); }


No creo que pueda ser más breve que esto, asegurando también la IEnumerator<T> del 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; } } } }

Editar: técnicamente idéntico a esta respuesta .


Podría ser:

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

Supongo que debería ser como de "Dónde" pero conservando el orden (?).


Podrías escribir:

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


Probablemente haga algo como esto:

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

Esta es una iteración con una verificación de que no es la última para cada vez.


Sería útil si .NET Framework se enviara con un método de extensión como este.

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(); } }


Si la velocidad es un requisito, esta forma de la vieja escuela debería ser la más rápida, a pesar de que el código no se ve tan bien como linq podría hacerlo.

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

Esto requiere que la secuencia sea una matriz, ya que tiene una longitud fija y elementos indexados.


Si no tiene tiempo para implementar su propia extensión, aquí hay una manera más rápida:

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


Si puede obtener el Count o la Length de un enumerable, que en la mayoría de los casos puede, simplemente 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);


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

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 shrink implementa un conteo simple hacia adelante para soltar el primero, left muchos elementos y el mismo búfer descartado para soltar los últimos elementos de la right .


Una ligera variación en la respuesta aceptada, 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); } }


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; } } } }