【问题标题】:alternatives to Enumerable.Count()==nEnumerable.Count()==n 的替代品
【发布时间】:2011-06-19 16:05:46
【问题描述】:

我正在为Enumerable.Count() == n 寻找更好的替代方案。我能想到的最好的是:

static class EnumerableExtensions
{
    public static bool CountEquals<T>(this IEnumerable<T> items, int n)
    {
        if (n <= 0) throw new ArgumentOutOfRangeException("n"); // use Any()

        var iCollection = items as System.Collections.ICollection;
        if (iCollection != null)
            return iCollection.Count == n;

        int count = 0;
        bool? retval = null;
        foreach (var item in items)
        {
            count++;

            if (retval.HasValue)
                return false;

            if (count == n)
                retval = true;
        }

        if (retval.HasValue)
            return retval.Value;

        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var items0 = new List<int>();
        var items1 = new List<int>() { 314 };
        var items3 = new List<int>() { 1, 2, 3 };
        var items5 = new List<int>() { 1, 2, 3, 4, 5 };
        var items10 = Enumerable.Range(0, 10);
        var itemsLarge = Enumerable.Range(0, Int32.MaxValue);

        Console.WriteLine(items0.CountEquals(3));
        Console.WriteLine(items1.CountEquals(3));
        Console.WriteLine(items3.CountEquals(3));
        Console.WriteLine(items5.CountEquals(3));
        Console.WriteLine(itemsLarge.CountEquals(3));
    }
}

我可以做得更好吗?有没有办法进一步概括这一点——通过比较?

【问题讨论】:

  • 您意识到Enumerable.Count 已经将List(或任何Collection,实际上)作为特殊情况处理,并且只是有效地返回Count 属性,对吧?

标签: c# linq count enumerable


【解决方案1】:

您可以使用Take 和Count 的组合来完全摆脱循环:

public static bool CountEquals<T>(this IEnumerable<T> items, int n)
{
  var iCollection = items as System.Collections.ICollection;
  if (iCollection != null)
    return iCollection.Count == n;
  return items.Take(n + 1).Count() == n;
}

【讨论】:

  • 然而,这仍然(有效地)在 Take() 和 Count() 中执行循环。
  • 是的,循环仍然存在;我发布的内容几乎等同于 Reed 的答案(或者 Mehrdad 的,真的)。
【解决方案2】:

使用Enumerable.Count 会比您上面的代码好得多。它已经在内部针对ICollection 进行了优化。

话虽如此,如果你必须保留你的扩展,你可以稍微简化一下循环:

int count = 0;
foreach (var item in items)
{
    count++;
    if(count > n)
        return false;
}
return count == n;

【讨论】:

  • using (var e = items.GetEnumerator()) { for (int i = 0; i &lt; n; ++i) if (!e.MoveNext()) return false; return !e.MoveNext(); }
  • @Dan: 是的 - 但是,如果你的“n”很小,我个人会使用collection.Take(n+1).Count() == n 让短路退出......
  • @Reed Ugly 但会起作用:try{ l.ElementAt(n) } catch (ArgumentOutOfRangeException ex) { }
  • @Magnus:可以,但我尽量避免使用异常处理来进行流控制。
  • @Magnus 这只是确认序列长度的下限是n,而不是上限。
【解决方案3】:

“更好”究竟是什么意思?快点?更轻松?

基本上,您似乎所做的是编写了一种专门针对一项特定任务进行优化的方法。您提到对其进行泛化,但它的性能优势源于它是如此具体(假设 具有性能优势——像 Count 这样的方法已经针对性能进行了相当大的调整,并且编译器也很漂亮擅长优化这样的东西)。

Premature optimization is the root of all evil. 如果这个特定操作的性能如此重要以至于值得用几十行代码替换二十多个字符的表达式xyz.Count() == abc,您可能想尝试其他提高性能的方法,例如重构.在大多数情况下,仅使用托管代码的开销就会使您获得的性能奖励(如果有的话)相形见绌。

话虽如此,如果您有 1000 万件物品,而您的目标数量要少得多,我很确定下面的内容会缩短迭代:

int count = 0;
var subset = items.TakeWhile(x => count++ < n + 1);
return count == n + 1;

易于阅读、易于维护,而且速度可能一样快。

【讨论】:

  • 值得一试。我仍然认为编译器可能会优化xyz.Count() == n,但如果不能,你可以试试 Stephen Cleary 的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-08
  • 2012-01-25
  • 2015-08-05
  • 2011-01-01
  • 2011-10-24
  • 2011-05-31
相关资源
最近更新 更多