2

What is the most performant way in Linq to write:

return enumerable.Count() > x;

I'm at least looking for a solution that:

  • doesn't involve counting out the whole enumerable.
  • preferably standard .NET Linq.
  • should work on different providers.

Note that enumerable.Any() works really well for more then 0, but I'm looking for a solution that checks for more than x.

Example:

Think of a very large enumerable that is build with yield return.

Dirk Boer
  • 8,522
  • 13
  • 63
  • 111
  • See this answer: http://stackoverflow.com/a/169699/961113 – Habib Jul 22 '14 at 19:28
  • Hi Habib, thanks for your answer. In this particular case (property that is not accessed by another query) it is evaluated. The Any() works really good for bigger then 0. But how to do bigger than `x`? – Dirk Boer Jul 22 '14 at 19:32
  • 1
    what is wrong with `return enumerable.Count() > x;`, I am not sure there is any other way exist to do the same. – Habib Jul 22 '14 at 19:33
  • Well Count() enumerates the whole list, while you already know after the `x`th item that you are done. – Dirk Boer Jul 22 '14 at 19:34
  • `Count()` doesn't necessarily enumerates the whole collection. http://stackoverflow.com/questions/168901/howto-count-the-items-from-a-ienumerablet-without-iterating/169699#169699 – Habib Jul 22 '14 at 19:36
  • 3
    About about simple `Skip` and `Any`? F.e: `return enumarble.Skip(x).Any();` ? –  Jul 22 '14 at 19:36
  • Hi Habib, thanks for answering. It doesn't *necessarily* enumerate the whole collection, but it will when I just access the property in plain code (i.e. not within a Linq to SQL) – Dirk Boer Jul 22 '14 at 19:41
  • @DirkBoer, for a `List`, there won't be any difference in `Count` property and `Count()` method. See http://stackoverflow.com/questions/4098186/list-count-vs-count-which-one-and-why – Habib Jul 22 '14 at 19:44
  • I've added an example to my question to make it more clear Habib. – Dirk Boer Jul 22 '14 at 19:45
  • Can anyone of the downvoters explain more? **primarily** opinion-based? Just because I like an answer that is standard .NET or works on other Linq providers? I think there are quite some concrete answers below with insightful reasoning. – Dirk Boer Jul 23 '14 at 07:56
  • This question is not opinion-based. It has a very specific set of requirements and an accepted answer. It is actually an interesting LINQ optimization question. – Moby Disk Jul 23 '14 at 14:38

6 Answers6

3

What about simple:

return enumerable.Skip(x).Any();

I think that is what you are looking for.

  • 4
    This _will_ enumerate collections that wouldn't have been enumerated by `Count()`. – D Stanley Jul 22 '14 at 19:40
  • 1
    Good point. So if you would really make it performant to all cases, this means you would need to do first a custom check if it is i.e. an IList? ... So I guess you go in the direction of writing your own extension method? – Dirk Boer Jul 22 '14 at 19:43
  • @DirkBoer Techincally you could check for `ICollection` since that's where `Count` is defined, but yes. Note that [`Enumerable.Count()`](http://referencesource.microsoft.com/System.Core/a.html#41ef9e39e54d0d0b) does exactly that. :) – D Stanley Jul 22 '14 at 19:44
  • 1
    @DirkBoer Yes, the most efficient solution for standard use cases would check for `ICollection` and use that Count property for comparison first since it is typically O(1). See my answer. – Mike Zboray Jul 22 '14 at 19:54
  • Except that it will still Enumerate all objects in a non-query context, @DStanley :) I have the feeling mike z's answer is going to be the closest. – Dirk Boer Jul 22 '14 at 19:58
  • @D Stanley yes, but the same in the oppsite way: when using `yield return` `Count()` will enumerate whole sequence, while `Skip(x).Any()` will enumerate only first `x` elements :) –  Jul 22 '14 at 20:06
  • @DirkBoer even enumerating in memory will be slower than returning a `Count` property that is likely available without the need for actually enumerating. – D Stanley Jul 22 '14 at 22:14
  • @pwas it will not enumerate if the underlying type implements `ICollection` and has a `Count` property that is pre-calculated (like `List` or `Array`). – D Stanley Jul 22 '14 at 22:15
  • @DStanley : like pwas was saying: `yield return`. That does not implement a Count() property :) it would even be possible to encounter infinite enumerables that way. – Dirk Boer Jul 23 '14 at 07:31
2

There's no built-in LINQ method that will give the most efficient solution. Basically you would want to check if the IEnumerable<T> has a Count property (via ICollection<T>) and if not enumerate it.

public static class MoreEnumerable
{
    public static bool HasAtLeast<T>(this IEnumerable<T> source, int count)
    {
        if (source is ICollection<T>)
        {
            return ((ICollection<T>)source).Count > count;
        }
        else if (source is ICollection)
        {
            return ((ICollection)source).Count > count;
        }

        return source.Skip(count).Any();
    }
}
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
1

What is the most performant (and beautiful) way in Linq to write:
return enumerable.Count() > x;

What's wrong with just:

return enumerable.Count() > x;

?

If the actual type of enumerable also implements ICollection, then it will just return ICollection.Count which is typically an O(1) opration. For IQueryable, most query providers will turn it into a SELECT COUNT(*) FROM ... which is about as fast as you can get.

So there are relatively few edge cases that will perform better than Count; probably few enough that trying to code for them is counter-productive.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • It's indeed more of a theoretical then a practical problem, but I've added a proper example. I.e. a *very* large `yield return` method. – Dirk Boer Jul 22 '14 at 19:54
1

I think you want something like this:

public static bool LazyCount<T>(this IEnumerable<T> source, int count)
{
    var enumerator = source.GetEnumerator();

    int i = 0;

    while(enumerator.MoveNext())
    {
        i++;
        if(i > count) return true;
    }
    return false;
}

The usage would be:

return enumerable.LazyCount(x);

Note: I'm really bad at naming but you should get the idea, you can modify it however you like.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
1

If you just want to use LINQ I would use something like

enumerable.Take(x).Count() == x

Even though I would prefer Selman22 approach

LazyOfT
  • 1,428
  • 7
  • 20
0

If you want to know if you have more than 5 you can do the following

enumerable.Take(6).Count() == 6

Luis Filipe
  • 8,488
  • 7
  • 48
  • 76