Pergunta

I'm looking for a better alternative for Enumerable.Count() == n. The best I've been able to come up with is:

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));
    }
}

Can I do any better? Is there a way to generalize this even more—passing in the comparision?

Foi útil?

Solução

You can use a combination of Take and Count to get rid of the loop entirely:

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;
}

Outras dicas

Using Enumerable.Count would be much better than your code above. It already optimizes for ICollection internally.

That being said, if you must keep your extension, you could simplify the loop a bit:

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

What exactly do you mean by "better"? Faster? Easier?

Essentially what you seem to have done is written a specialized method that's optimized for one specific task. You mention generalizing it, but its performance advantage stems from the fact that it's so specific (assuming there is a performance advantage--methods like Count are already tuned pretty hard for performance, and the compiler's pretty good at optimizing stuff like this).

Premature optimization is the root of all evil. If the performance of this particular operation is so important that it's worth replacing the twenty-odd-character expression xyz.Count() == abc with dozens of lines of code, you may want to try other methods of increasing performance, like refactoring. For most situations, just the overhead from using managed code is going to dwarf the performance bonus you get (if any).

That being said -- if you have something like 10 million items, and your target count is a whole lot less, I'm pretty sure the below will short-circuit the iteration:

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

Easy to read, easy to maintain, and probably just as fast.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top