How can I efficiently determine if an IEnumerable has more than one element?
给定一个初始化的IEnumerable:
1
| IEnumerable<T> enumerable; |
我想确定它是否有多个元素。我认为最明显的方法是:
但是,我相信Count()列举了整个集合,这对于这个用例来说是不必要的。例如,如果集合包含大量元素或从外部源提供其数据,那么在性能方面这可能是非常浪费的。
如何在不枚举任何超过2个元素的情况下完成此操作?
- 如果你必须依靠它,那么做一些事情,你最好不要强迫它评估成一个列表或其他集合。
- @Devshort,你的意思是依靠它工作还是使用Count()方法?
- 我的意思是,如果你需要找出它上面有多少个元素,然后根据这些元素做一些事情,你也可以对它进行评估。Count将对它进行一次评估,然后如果您再次迭代它,则必须再次对所有内容进行评估。与其他任何IEnumerable方法相同,如Take或Skip。这真的取决于数据是什么。
- @Devshort,谢谢;我同意不要重复列举是很重要的。但是,请记住,也可能存在这样的情况:您只关心这个单一的标准,而不想对可枚举进行任何其他工作。
- @Devshort,你是在暗示Take和Skip枚举整个可枚举的吗?我还没有研究过实现,但我认为情况并非如此,因为它效率低下,技术上没有必要。
- 如果基础IEnumerable是Array、ICollection或IQueryable作为Count()被优化以检测它们并适当地使用长度/计数属性,您可能会考虑使用Count()。
- 如果IEnumerable包含超过1个元素而不计数,则可能重复测试
通过结合System.Linq中的扩展方法,可以用多种方法测试这个问题。下面是两个简单的例子:
1 2
| bool twoOrMore = enumerable.Skip(1).Any();
bool twoOrMoreOther = enumerable.Take(2).Count() == 2; |
我更喜欢第一个,因为有一个常见的方法来检查Count() >= 1是否与Any()在一起,因此我发现它更可读。
- 这并不总是有效的,如果这是一个数据流,则会丢失跳过和/或获取的数据。此外,如果每次对可枚举项执行操作时数据都在更改,这意味着完成此测试后,对数据的评估可能会有所不同。
- @但他只关心计数,而不是实际数据。
- @Devshort是真的。当然,如果需要,可以将take(2)结果保存到变量中,但这不是问题中的一个要求。
- @Dennisrongo,你是对的,但他也在问IEnumerable是在大型数据源还是外部流的基础上,在这种情况下,对它执行take/skip操作,你就丢失了数据。现在,您需要创建一个新的可枚举项来为所获取的数据加前缀。可能,只是丑陋。
- @Devshort,在不丢失数据的情况下,通过列举至少2个元素来满足我的标准是不是不可能的?
- @Devshort提到使用外部数据源只是为了强调不枚举超过必要数量的元素的重要性。对于这个问题,我不关心其他的问题。为了澄清这一点,我稍微重新措辞了一下。
- @Devshort,在看到另一个建议预取IEnumerable的答案之后,我现在可以看到如何在没有潜在数据丢失/损坏的情况下实现这一点。
- @萨姆,这也是我们要做的。它只是不保存中间结果,中间结果很容易保存(如Take(2).ToList())。
- @camerons,预取/缓存IEnumerable解决方案应允许您在不启动新枚举的情况下重新使用可枚举,我认为这将缓解每个枚举的数据不同的问题。我不是说你对我的问题的解决方法有问题;我只是更正了我之前的一个评论,我错误地认为不可能按照@devshort的建议去做。
为了好玩,调用next()两次,然后再获得一个IEnumerable。
或者,为这个特定的目标编写一个小包装类:EnumerablePrefetcher : IEnumerable以尝试在初始化时获取指定数量的项。
它的IEnumerable GetItems()方法应该以这种方式使用收益率。
1 2 3 4 5 6 7 8
| foreach (T item in prefetchedItems) // array of T, prefetched and decided if IEnumerable has at least n elements
{
yield return item;
}
foreach (T item in otherItems) // IEnumerable<T>
{
yield return item;
} |
- 谢谢,我没想过要预取或缓存IEnumerable!
- 代码样本可以表示为return prefetchedItems.Concat(otherItems);,尽管上面的代码也是实心的。
- @卡麦隆,我刚刚意识到,Concat是存在的,谢谢!我认为框架中缺少这样的功能。
@Cameron-s的解决方案更简单,但下面的方案更有效。我根据Enumerable.Count()方法提出了这个问题。Skip()总是迭代而不是短路,以获得ICollection或ICollection类型的source计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| /// <summary>
/// Returns true if source has at least <paramref name="count"/> elements efficiently.
/// </summary>
/// <remarks>Based on int Enumerable.Count() method.</remarks>
public static bool HasCountOfAtLeast <TSource >(this IEnumerable <TSource > source, int count )
{
source .ThrowIfArgumentNull("source");
var collection = source as ICollection <TSource >;
if (collection != null)
{
return collection .Count >= count ;
}
var collection2 = source as ICollection ;
if (collection2 != null)
{
return collection2 .Count >= count ;
}
int num = 0;
checked
{
using (var enumerator = source .GetEnumerator())
{
while (enumerator .MoveNext())
{
num ++;
if (num >= count )
{
return true;
}
}
}
}
return false; // < count
} |