I was reading this question, and the answer that mentioned streams, and a thought had occurred to me.
In many cases (at least in my usage) I want to determine if a sequence (or IEnumerable
) has at least a certain number of elements, or has no more than a certain number, etc, that all match a predicate.
As it stands now, we have the IEnumerable<T>.Count(Func<T, bool>)
method, but it will enumerate the entire IEnumerable<T>
before returning. In some cases, we know we only care if there are certain numbers of values. (Such as exactly 8.)
As a result, I've built out a set of new extension methods to IEnumerable<T>
, that add some functionality for counting only as needed. That is, if we care that we only have at least n
, we can now deal with that in particular.
public static class IEnumerableTCountExtensions
{
public static bool CountAtLeast<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count == required) { break; }
}
}
return count == required;
}
public static bool CountAbove<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count > required) { break; }
}
}
return count > required;
}
public static bool CountEqual<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count > required) { return false; }
}
}
return count == required;
}
public static bool CountNotEqual<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count > required) { return true; }
}
}
return count != required;
}
public static bool CountBelow<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count >= required) { return false; }
}
}
return count < required;
}
public static bool CountNoMoreThan<T>(this IEnumerable<T> items, Func<T, bool> predicate, int required)
{
var count = 0;
foreach (T item in items)
{
if (predicate(item))
{
count++;
if (count == required) { return false; }
}
}
return count <= required;
}
}
There are 6 functions: one for each of the following:
>=
:CountAtLeast
;>
:CountAbove
;==
:CountEqual
;!=
:CountNotEqual
;<
:CountBelow
;<=
:CountNoMoreThan
;
Each of them short-circuits as reasonable, to allow us to only enumerate what we have to. The short-circuit path is taken in the case that it's acceptable.
A quick set of tests, with a pass/fail for each, is:
var things = new[] { 1, 2, 3, 4, 5 }.AsEnumerable().Select(x => { Console.WriteLine(x); return x; });
Console.WriteLine("T? " + things.CountAtLeast(x => x <= 5, 2));
Console.WriteLine("F? " + things.CountAtLeast(x => x <= 5, 6));
Console.WriteLine("T? " + things.CountAbove(x => x <= 5, 2));
Console.WriteLine("F? " + things.CountAbove(x => x <= 5, 5));
Console.WriteLine("F? " + things.CountEqual(x => x <= 5, 2));
Console.WriteLine("T? " + things.CountEqual(x => x <= 5, 5));
Console.WriteLine("F? " + things.CountEqual(x => x <= 5, 6));
Console.WriteLine("T? " + things.CountNotEqual(x => x <= 5, 2));
Console.WriteLine("F? " + things.CountNotEqual(x => x <= 5, 5));
Console.WriteLine("T? " + things.CountNotEqual(x => x <= 5, 6));
Console.WriteLine("T? " + things.CountBelow(x => x <= 5, 6));
Console.WriteLine("F? " + things.CountBelow(x => x <= 5, 2));
Console.WriteLine("T? " + things.CountNoMoreThan(x => x <= 5, 6));
Console.WriteLine("F? " + things.CountNoMoreThan(x => x <= 5, 2));
The Console.WriteLine(x);
is included to demonstrate that each item is only being iterated as maximally necessary, and can be removed for easier visual.