Is there a LINQ extension or (a sensible/efficient set of LINQ entensions) that determine whether a collection has at least 'x' elements?
我有代码需要知道集合不应该是空的或只包含一个项。
一般来说,我希望表单的扩展名为:
bool collectionHasAtLeast2Items = collection.AtLeast(2);
我可以很容易地编写扩展,枚举集合并增加索引器,直到达到所请求的大小,或者元素用完,但是linq框架中是否已经有了可以这样做的东西?我的想法(按我的想法顺序)是:
bool collectionHasAtLeast2Items = collection.Take(2).Count() == 2;或
bool collectionHasAtLeast2Items = collection.Take(2).ToList().Count == 2;
虽然(在文档中)没有定义获取的元素多于集合所包含的元素的行为是可枚举的,但take方法似乎可以实现预期的效果。
这不是最有效的解决方案,要么枚举一次以获取元素,然后再次枚举以对其进行计数(这是不必要的),要么枚举一次以获取元素,然后构造一个列表以获取非枚举器-y的Count属性,因为我实际上不需要该列表。
这并不是很漂亮,因为我总是要做出两个断言,第一个是"x",然后检查我是否真的收到了"x",这取决于未记录的行为。
或者我可以使用:
bool collectionHasAtLeast2Items = collection.ElementAtOrDefault(2) != null;
然而,这在语义上并不清楚。也许最好是用一个方法名来包装它,这意味着我想要什么。我假设这将是有效的,我没有考虑到代码。
其他一些想法正在使用Last(),但我明确不想枚举整个集合。
或者可能是Skip(2).Any(),同样不是完全明显的语义,但比ElementAtOrDefault(2) != null更好,尽管我认为它们会产生相同的结果?
有什么想法吗?
- 为什么不创建一个集合、加载一些数据和测试?
- 您链接到的页面上有一条注释Take enumerates source and yields elements until count elements have been yielded or source contains no more elements.,可以理解为"如果源列表短于计数,则仅返回该短列表"。
- @汉斯克斯汀-它可能说,直到律师得到了它:)
- @汉克斯汀:是的,也许你是对的,谢谢。
- 顺便说一下,Take(n).Count()不会枚举两次:Take方法的结果流得很慢,所以您最需要的是一次通过n项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static bool AtLeast<T>(this IEnumerable<T> source, int count)
{
// Optimization for ICollection<T>
var genericCollection = source as ICollection<T>;
if (genericCollection != null)
return genericCollection.Count >= count;
// Optimization for ICollection
var collection = source as ICollection;
if (collection != null)
return collection.Count >= count;
// General case
using (var en = source.GetEnumerator())
{
int n = 0;
while (n < count && en.MoveNext()) n++;
return n == count;
}
} |
- 一般情况下的代码可以包装成LINQ方法,不是吗?
- @Abatishchev,是的,它可以,但是如果您直接操纵枚举器,开销会更少(是的,这是一个微优化…)
- 那么整个代码完全可以在没有LINQ的情况下编写,速度会快一点,但是版权问题会出现:)
- 谢谢,这是一个很好的手工方法,尽管我想我会选择Skip(n).Any(),因为我更喜欢在我能做到的地方使用内置的行为,这最清楚地表明了我的意图——我想。然而,Take()似乎比Skip()更快,但这似乎是一种微观优化。
- 考虑到与您的代码非常相似的Count()代码,它还在n++部分添加了"checked"语句。另外,可能应该有一个防护装置(计数大于等于0)。
如果序列实现了ICollection,那么可以使用Count() >= 2?
在场景后面,Enumerable.Count()扩展方法检查循环下的序列是否实现ICollection。如果确实如此,则返回Count属性,因此目标性能应为o(1)。
因此,((IEnumerable)((ICollection)sequence)).Count() >= x也应该有O(1)。
- 我认为他指的是除count()之外的更高性能的方法。
- @艾瑞克:我的探索展示了O(1)的表现。
- 我认为这是一个很好的方法,考虑到IEnumerable的局限性,在最一般的情况下,你最好只列举两个项目。"免费"获得EDOCX1[1]的优化是一个不错的胜利。
- @abatishchev,如果集合实现了ICollection,则为o(1),但一般情况下不是。在某些情况下,当您只需要检查是否至少有2个项目时,执行完整计数可能会比较昂贵…
- @托马斯利夫斯基:同意,肯德尔的回答更好地揭示了这一点。
- 我特别不想使用Count(),因为我不关心集合中有多少元素,而且可能会有很多元素,所以当我只对前2感兴趣时,数到10000个似乎不明智。另外,它们将是IQueryable,因此我更喜欢在执行查询之前尽可能地提高LINQ的效率。
你可以使用Count,但如果性能有问题,你最好使用Take。
1
| bool atLeastX = collection.Take(x).Count() == x; |
由于Take(我相信)使用了延迟执行,它只会执行一次收集。
Abatishchev提到,Count与ICollection是O(1),所以你可以这样做,并得到最好的两个世界。
1 2 3 4 5 6 7 8 9 10 11 12 13
| IEnumerable <int> col ;
// set col
int x ;
// set x
bool atLeastX ;
if (col is ICollection <int>)
{
atLeastX = col .Count() >= x ;
}
else
{
atLeastX = col .Take(x ).Count() == x ;
} |
你也可以使用Skip/Any,事实上,我打赌它会比Take/Count更快。
- 我一直认为O(1)是尽可能快的,是不是错了?(我指的是"……会更快"部分)
- 更新了,很抱歉弄混了。
- @亚历克斯:实际上是O(1)+C1<>O(1)+C2,如果是C1,C2是常数,可能会有所不同。因此,结果的性能不取决于元素的数量,而只取决于基础代码。
- @我的意思是:我一直假设,Count()是o(1)[+c,ofc],没有更快的方法来执行这种检查。现在我读到Skip/Any方法可能更快,并且产生了疑问。
- @Kendallfrey:这是我的解决方案之一,但我认为使用Skip(n).Any()更有效,并且对其含义稍微清楚一些。我不愿意使用Count(),除非我真正关心枚举中有多少元素。