Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a LINQ extension or (a sensible/efficient set of LINQ entensions) that determine whether a collection has at least 'x' elements?

I have code that needs to know that a collection should not be empty or contain only one item.

In general, I want an extension of the form:

bool collectionHasAtLeast2Items = collection.AtLeast(2);

I can write an extension easily, enumerating over the collection and incrementing an indexer until I hit the requested size, or run out of elements, but is there something already in the LINQ framework that would do this? My thoughts (in order of what came to me) are::

bool collectionHasAtLeast2Items = collection.Take(2).Count() == 2; or

bool collectionHasAtLeast2Items = collection.Take(2).ToList().Count == 2;

Which would seem to work, though the behaviour of taking more elements than the collection contains is not defined (in the documentation) Enumerable.Take Method , however, it seems to do what one would expect.

It's not the most efficient solution, either enumerating once to take the elements, then enumerating again to count them, which is unnecessary, or enumerating once to take the elements, then constructing a list in order to get the count property which isn't enumerator-y, as I don't actually want the list.

It's not pretty as I always have to make two assertions, first taking 'x', then checking that I actually received 'x', and it depends upon undocumented behaviour.

Or perhaps I could use:

bool collectionHasAtLeast2Items = collection.ElementAtOrDefault(2) != null;

However, that's not semantically-clear. Maybe the best is to wrap that with a method-name that means what I want. I'm assuming that this will be efficient, I haven't reflected on the code.

Some other thoughts are using Last(), but I explicitly don't want to enumerate through the whole collection.

Or maybe Skip(2).Any(), again not semantically completely obvious, but better than ElementAtOrDefault(2) != null, though I would think they produce the same result?

Any thoughts?

like image 391
nicodemus13 Avatar asked May 14 '12 12:05

nicodemus13


3 Answers

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;
    }
}
like image 186
Thomas Levesque Avatar answered Nov 20 '22 18:11

Thomas Levesque


You can use Count() >= 2, if you sequence implements ICollection?


Behind the scene, Enumerable.Count() extension method checks does the sequence under loop implements ICollection. If it does indeed, Count property returned, so target performance should be O(1).

Thus ((IEnumerable<T>)((ICollection)sequence)).Count() >= x also should have O(1).

like image 22
abatishchev Avatar answered Nov 20 '22 19:11

abatishchev


You could use Count, but if performance is an issue, you will be better off with Take.

bool atLeastX = collection.Take(x).Count() == x;

Since Take (I believe) uses deferred execution, it will only go through the collection once.

abatishchev mentioned that Count is O(1) with ICollection, so you could do something like this and get the best of both worlds.

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

You could also use Skip/Any, in fact I bet it would be even faster than Take/Count.

like image 3
Kendall Frey Avatar answered Nov 20 '22 19:11

Kendall Frey