如何使用LINQ获取序列中的最后一个元素?
假设我有一个序列。
IEnumerable<int> sequence = GetSequenceFromExpensiveSource(); // sequence now contains: 0,1,2,3,...,999999,1000000
获取序列并不便宜,并且是dynamic生成的,我只想遍历它一次。
我想得到0 – 999999(即最后一个元素除外)
我承认我可以做一些事情:
sequence.Take(sequence.Count() - 1);
但是这导致在大序列上的两个枚举。
是否有一个LINQ结构,可以让我这样做:
sequence.TakeAllButTheLastElement();
我不知道一个Linq解决scheme – 但是你可以很容易地使用生成器自己编码algorithm(yield return)。
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())); }
或者作为放弃最后n个项目(使用评论中build议的队列)的通用解决scheme:
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())); }
因为我不是明确使用Enumerator
的粉丝,所以这里有一个select。 请注意,包装器方法需要让无效参数提前抛出,而不是推迟检查,直到序列被实际枚举为止。
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; } }
根据Eric Lippert的build议,它很容易推广到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(); } }
现在我在缓冲之前缓冲而不是在产生之后,所以n == 0
情况不需要特殊的处理。
作为创build自己的方法的替代方法,在元素顺序不重要的情况下,下一个将会工作:
var result = sequence.Reverse().Skip(1);
BCL(或者我认为MoreLinq)没有任何东西,但是你可以创build你自己的扩展方法。
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; } } }
如果.NET框架像这样使用扩展方法,那将会很有帮助。
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(); } }
Joren优雅的解决scheme略有扩展:
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); }
在哪里收缩实现一个简单的计数向前丢弃第一个left
许多元素和相同的丢弃的缓冲区,以放弃最后right
许多元素。
如果您没有时间推出自己的扩展程序,可以使用以下方法:
var next = sequence.First(); sequence.Skip(1) .Select(s => { var selected = next; next = s; return selected; });
在接受的答案,这(为我的口味)略有变化有点简单:
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); } }
我用这个问题的解决scheme稍微复杂些。
我的util static类包含一个扩展方法MarkEnd
,它转换EndMarkedItem<T>
-items中的EndMarkedItem<T>
Item。 每个元素都标有一个额外的int
,它是0 ; 或者(如果对最后3个项目特别感兴趣)最后3个项目的-3 , -2或-1 。
这可能是有用的, 例如,当你想在一个简单的foreach
创build一个列表 – 在除了最后2个元素之外的每个元素之后用逗号分隔,而倒数第二个元素后跟一个连接词(比如“ and “或” 或 “),最后一个元素后跟一个点。
为了生成没有最后n个项目的整个列表,扩展方法ButLast
简单地迭代EndMarkedItem<T>
s,而EndMark == 0
。
如果不指定tailLength
,则只有最后一个项目被标记(在MarkEnd()
)或被删除(在ButLast()
)。
像其他解决scheme一样,这是通过缓冲工作的。
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); } } } } } }
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; } } } }
如果你可以得到一个枚举的Count
或Length
,在大多数情况下,你可以,然后Take(n - 1)
数组的例子
int[] arr = new int[] { 1, 2, 3, 4, 5 }; int[] sub = arr.Take(arr.Length - 1).ToArray();
IEnumerable<T>
示例
IEnumerable<int> enu = Enumerable.Range(1, 100); IEnumerable<int> sub = enu.Take(enu.Count() - 1);
这是一个普遍和恕我直言的优雅的解决scheme,将正确处理所有情况:
using System; using System.Collections.Generic; using System.Linq; public class Program { public static void Main() { var r = Enumerable.Range(1, 20); foreach (int i in r.AllButLast(3)) Console.WriteLine(i); } } public static class LinqExt { public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1) { var queue = new Queue<T>(n); IEnumerator<T> enumerator = enumerable.GetEnumerator(); for(int i=0; i<n && enumerator.MoveNext(); i++) queue.Enqueue(enumerator.Current); while (enumerator.MoveNext()) { queue.Enqueue(enumerator.Current); yield return queue.Dequeue(); } } }
你可以写:
var list = xyz.Select(x=>x.Id).ToList(); list.RemoveAt(list.Count - 1);
我不认为它可以得到比这更简洁 – 也确保configurationIEnumerator<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; } } } }
编辑:技术上相同的这个答案 。
可能:
var allBuLast = sequence.TakeWhile(e => e != sequence.Last());
我想它应该像德“在哪里”,但保持秩序(?)。
我的传统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; } }
如果速度是一个要求,这个老派的方式应该是最快的,即使代码看起来不像linq可以做到的那样stream畅。
int[] newSequence = int[sequence.Length - 1]; for (int x = 0; x < sequence.Length - 1; x++) { newSequence[x] = sequence[x]; }
这要求该序列是一个数组,因为它具有固定的长度和索引项目。
为什么不只是.ToList<type>()
在序列上,然后调用计数,并采取像你原来的.ToList<type>()
但因为它被拉入列表,它不应该做一个昂贵的枚举两次。 对?
我可能会做这样的事情:
sequence.Where(x => x != sequence.LastOrDefault())
这是一个迭代,检查它不是每个时间的最后一个。