Home > Blockchain >  Equivalent of Linq's Any() function for when a collection has multiple items
Equivalent of Linq's Any() function for when a collection has multiple items

Time:08-22

In C#, using Linq, if I want to check if a collection has any elements, I can do this:

someCollection.Any()

which is preferable over

someCollection.Count() > 0

since the latter will count all the items in the collection, when I don't really care how many items there are, I just want to know if there are any.

Is there an equivalent for checking if a collection has more than one item? Something like:

someCollection.Many()

instead of having to do

someCollection.Count() > 1

Silly question, I know, and from my research, it doesn't look like there is. But since it's something I use frequently, I thought I'd double check with the community.

Thanks

CodePudding user response:

As per my comment:

First, you should check to see if IEnumerable<T> source actually is an IReadOnlyCollection<T> or ICollection<T> because that has a .Count property you can use - which would be preferable to any iteration.

Assuming your IEnumerable<T> does not have an O(1) .Count property, if you want to see if there's at least 1 element (i.e. "at least 2 or more") then use source.Take(2).Count() == 2 or source.Skip(1).Any().

Like so:

public static Boolean Many<T>( this IEnumerable<T> source )
{
    if( source is null ) throw new ArgumentNullException(nameof(source));

    if( source is ICollection<T> col ) return col.Count >= 2;
    else if( source is IReadOnlyCollection<T> roCol ) return roCol.Count >= 2;

    return source.Take(2).Count() == 2;
}

If you want to be more efficient about it, do manual iteration:

public static Boolean Many<T>( this IEnumerable<T> source )
{
    if( source is null ) throw new ArgumentNullException(nameof(source));

    if( source is ICollection<T> col ) return col.Count >= 2;
    else if( source is IReadOnlyCollection<T> roCol ) return roCol.Count >= 2;

    Int32 count = 0;
    using( IEnumerator<T> iter = source.GetEnumerator() )
    {
        while( iter.MoveNext() && count < 2 )
        {
            count  = 1;
        }
    }

    return count == 2;
}

If you want to be even more efficient about it, allow consumers to supply non-boxed enumerators (e.g. List<T>.Enumerator):

public static Boolean Many<TEnumerable,TEnumerator,TElement>( this IEnumerable<T> source, Func<TEnumerable,TEnumerator> getEnumerator )
    where TEnumerable : IEnumerable<T>
    where TEnumerator : IEnumerator<T>
{
    if( source is null ) throw new ArgumentNullException(nameof(source));
    if( getEnumerator  is null ) throw new ArgumentNullException(nameof(getEnumerator));

    if( source is ICollection<T> col ) return col.Count >= 2;
    else if( source is IReadOnlyCollection<T> roCol ) return roCol.Count >= 2;

    Int32 count = 0;
    using( TEnumerator iter = getEnumerator( source ) )
    {
        while( iter.MoveNext() && count < 2 )
        {
            count  = 1;
        }
    }

    return count == 2;
}

Used like so:

List<String> listOfStrings = new List<String>() { ... };

if( listOfStrings.Many</*TEnumerable:*/ List<String>, /*TEnumerator:*/ List<String>.Enumerator, /*TElement:*/ String >( l => l.GetEnumerator() ) )
{
    
}
  • Yes, it's ugly... it's unfortunate, but C# still doesn't support this level of generic type-inference - nor does it support partial generic parameter application.
  • The list.GetEnumerator() part is needed as it's the only way to pass a struct-based enumerator into generic code without boxing (at least, not without reflection).
  • Related