给定一个集合,有没有办法得到该集合的最后N个元素?如果框架中没有方法,那么编写一个扩展方法来实现这个目的的最佳方式是什么?
当前回答
使用EnumerableEx。在RX的系统中使用last。交互式装配。这是一个类似于@Mark的O(N)实现,但它使用队列而不是环形缓冲区结构(并且当达到缓冲区容量时将项目从队列中移除)。
(注意:这是IEnumerable版本-不是IObservable版本,尽管两者的实现几乎相同)
其他回答
如果可以选择使用第三方库,则MoreLinq定义了TakeLast()。
我试图将效率和简单性结合起来,最后得到了这样的结果:
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>,如果是,则直接使用数组索引提取最后的值。
使用EnumerableEx。在RX的系统中使用last。交互式装配。这是一个类似于@Mark的O(N)实现,但它使用队列而不是环形缓冲区结构(并且当达到缓冲区容量时将项目从队列中移除)。
(注意:这是IEnumerable版本-不是IObservable版本,尽管两者的实现几乎相同)
使用LINQ获取集合的最后N有点低效,因为所有上述解决方案都需要遍历集合。TakeLast(int n) in System。Interactive也存在这个问题。
如果你有一个列表,更有效的方法是使用下面的方法进行切片
/// Select from start to end exclusive of end using the same semantics
/// as python slice.
/// <param name="list"> the list to slice</param>
/// <param name="start">The starting index</param>
/// <param name="end">The ending index. The result does not include this index</param>
public static List<T> Slice<T>
(this IReadOnlyList<T> list, int start, int? end = null)
{
if (end == null)
{
end = list.Count();
}
if (start < 0)
{
start = list.Count + start;
}
if (start >= 0 && end.Value > 0 && end.Value > start)
{
return list.GetRange(start, end.Value - start);
}
if (end < 0)
{
return list.GetRange(start, (list.Count() + end.Value) - start);
}
if (end == start)
{
return new List<T>();
}
throw new IndexOutOfRangeException(
"count = " + list.Count() +
" start = " + start +
" end = " + end);
}
with
public static List<T> GetRange<T>( this IReadOnlyList<T> list, int index, int count )
{
List<T> r = new List<T>(count);
for ( int i = 0; i < count; i++ )
{
int j=i + index;
if ( j >= list.Count )
{
break;
}
r.Add(list[j]);
}
return r;
}
以及一些测试用例
[Fact]
public void GetRange()
{
IReadOnlyList<int> l = new List<int>() { 0, 10, 20, 30, 40, 50, 60 };
l
.GetRange(2, 3)
.ShouldAllBeEquivalentTo(new[] { 20, 30, 40 });
l
.GetRange(5, 10)
.ShouldAllBeEquivalentTo(new[] { 50, 60 });
}
[Fact]
void SliceMethodShouldWork()
{
var list = new List<int>() { 1, 3, 5, 7, 9, 11 };
list.Slice(1, 4).ShouldBeEquivalentTo(new[] { 3, 5, 7 });
list.Slice(1, -2).ShouldBeEquivalentTo(new[] { 3, 5, 7 });
list.Slice(1, null).ShouldBeEquivalentTo(new[] { 3, 5, 7, 9, 11 });
list.Slice(-2)
.Should()
.BeEquivalentTo(new[] {9, 11});
list.Slice(-2,-1 )
.Should()
.BeEquivalentTo(new[] {9});
}
如果你不介意将Rx作为单子的一部分,你可以使用TakeLast:
IEnumerable<int> source = Enumerable.Range(1, 10000);
IEnumerable<int> lastThree = source.AsObservable().TakeLast(3).AsEnumerable();
推荐文章
- HTTP POST返回错误:417“期望失败。”
- 如何在。net中创建和使用资源
- 为什么Path。以Path.DirectorySeparatorChar开头的文件名合并不正确?
- 如何在c#中获得正确的时间戳
- Linq选择列表中存在的对象(A,B,C)
- c# .NET中的App.config是什么?如何使用它?
- c#:如何获得一个字符串的第一个字符?
- String类中的什么方法只返回前N个字符?
- 更好的方法将对象转换为int类型
- 我可以将c#字符串值转换为转义字符串文字吗?
- 在c#中转换char到int
- c#中朋友的对等物是什么?
- 关键字使用virtual+override vs. new
- 在ASP中选择Tag Helper。NET Core MVC
- 如何在没有任何错误或警告的情况下找到构建失败的原因