给定一个集合,有没有办法得到该集合的最后N个元素?如果框架中没有方法,那么编写一个扩展方法来实现这个目的的最佳方式是什么?
当前回答
使用EnumerableEx。在RX的系统中使用last。交互式装配。这是一个类似于@Mark的O(N)实现,但它使用队列而不是环形缓冲区结构(并且当达到缓冲区容量时将项目从队列中移除)。
(注意:这是IEnumerable版本-不是IObservable版本,尽管两者的实现几乎相同)
其他回答
注意:我错过了你的问题标题说使用Linq,所以我的回答实际上没有使用Linq。
如果希望避免缓存整个集合的非惰性副本,可以编写一个使用链表的简单方法。
下面的方法将把它在原始集合中找到的每个值添加到一个链表中,并将链表修剪到所需的项数。由于它通过遍历集合一直将链表修剪为这个数量的项,因此它只保留原始集合中最多N个项的副本。
它不要求您知道原始集合中项目的数量,也不需要对其进行多次迭代。
用法:
IEnumerable<int> sequence = Enumerable.Range(1, 10000);
IEnumerable<int> last10 = sequence.TakeLast(10);
...
扩展方法:
public static class Extensions
{
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> collection,
int n)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
if (n < 0)
throw new ArgumentOutOfRangeException(nameof(n), $"{nameof(n)} must be 0 or greater");
LinkedList<T> temp = new LinkedList<T>();
foreach (var value in collection)
{
temp.AddLast(value);
if (temp.Count > n)
temp.RemoveFirst();
}
return temp;
}
}
我试图将效率和简单性结合起来,最后得到了这样的结果:
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
{
if (source == null) { throw new ArgumentNullException("source"); }
Queue<T> lastElements = new Queue<T>();
foreach (T element in source)
{
lastElements.Enqueue(element);
if (lastElements.Count > count)
{
lastElements.Dequeue();
}
}
return lastElements;
}
关于 在c#中,Queue<T>是使用循环缓冲区实现的,因此每次循环都没有对象实例化(只有当队列增长时)。我没有设置队列容量(使用专用构造函数),因为有人可能使用count = int调用此扩展。MaxValue。为了获得额外的性能,您可以检查源实现IList是否<T>,如果是,则直接使用数组索引提取最后的值。
collection.Skip(Math.Max(0, collection.Count() - N));
这种方法保留了项目的顺序,不依赖于任何排序,并且在多个LINQ提供者之间具有广泛的兼容性。
重要的是要注意不要使用负数调用Skip。一些提供程序,比如实体框架,会在提供一个否定的参数时产生一个ArgumentException。对数学的呼唤。马克斯巧妙地避免了这一点。
下面的类具有扩展方法的所有基本要素,即:静态类、静态方法和this关键字的使用。
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));
}
}
关于性能的简要说明:
因为对Count()的调用可能导致某些数据结构的枚举,这种方法有导致两次数据传递的风险。对于大多数枚举对象来说,这并不是真正的问题;事实上,对于list、array甚至EF查询,已经有了优化,可以在O(1)时间内计算Count()操作。
但是,如果您必须使用只向前的枚举对象,并且希望避免进行两次传递,则可以考虑Lasse V. Karlsen或Mark Byers描述的一次传递算法。这两种方法都使用临时缓冲区来保存枚举时的项,一旦找到集合的末尾,就会产生这些项。
老实说,我对这个答案并不是特别自豪,但对于小的集合,你可以使用以下方法:
var lastN = collection.Reverse().Take(n).Reverse();
有点俗气,但它的工作;)
下面是一个方法,它适用于任何枚举对象,但只使用O(N)个临时存储:
public static class TakeLastExtension
{
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int takeCount)
{
if (source == null) { throw new ArgumentNullException("source"); }
if (takeCount < 0) { throw new ArgumentOutOfRangeException("takeCount", "must not be negative"); }
if (takeCount == 0) { yield break; }
T[] result = new T[takeCount];
int i = 0;
int sourceCount = 0;
foreach (T element in source)
{
result[i] = element;
i = (i + 1) % takeCount;
sourceCount++;
}
if (sourceCount < takeCount)
{
takeCount = sourceCount;
i = 0;
}
for (int j = 0; j < takeCount; ++j)
{
yield return result[(i + j) % takeCount];
}
}
}
用法:
List<int> l = new List<int> {4, 6, 3, 6, 2, 5, 7};
List<int> lastElements = l.TakeLast(3).ToList();
它的工作原理是使用一个大小为N的环形缓冲区来存储它看到的元素,用新元素覆盖旧元素。当到达枚举对象的末尾时,循环缓冲区包含最后N个元素。
推荐文章
- 实体框架核心:在上一个操作完成之前,在此上下文中开始的第二个操作
- 如何为构造函数定制Visual Studio的私有字段生成快捷方式?
- 如何使用JSON确保字符串是有效的JSON。网
- AppSettings从.config文件中获取值
- 通过HttpClient向REST API发布一个空体
- 如何检查IEnumerable是否为空或空?
- 自动化invokerrequired代码模式
- 在c#代码中设置WPF文本框的背景颜色
- 在c#中,什么是单子?
- c#和Java中的泛型有什么不同?和模板在c++ ?
- c#线程安全快速(est)计数器
- 如何将此foreach代码转换为Parallel.ForEach?
- 如何分裂()一个分隔字符串到一个列表<字符串>
- 如何转换列表<字符串>列表<int>?
- c#对象列表,我如何得到一个属性的和