15

Question: given IEnumerable<>, how to check what sequence contains more than x items?


MCVE:

static void Main(string[] args)
{
    var test = Test().Where(o => o > 2 && o < 6); // ToList()
    if (test.Count() > 1) // how to optimize this?
        foreach (var t in test) // consumer
            Console.WriteLine(t);
}

static IEnumerable<int> Test()
{
    for (int i = 0; i < 10; i++)
        yield return i;
}

The problem here is what Count() will run complete sequence and that's 1E6+ items (ToList() is also bad idea). I am also not allowed to change consumer code (it's a method accepting complete sequence).

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • 1
    What about `Any`? – Pikoh Nov 02 '16 at 13:02
  • 1
    `Any()` should be fine surely? `Count()` enumerates the entire collection unlike `Any` which determines whether a sequence contains any elements. – Ric Nov 02 '16 at 13:04
  • @Pikoh, my bad, `Any()` was running sequence until first item what will match `Where()` conditions. Yes, `Any()` will do for case when `x = 1`. – Sinatr Nov 02 '16 at 13:04
  • 2
    Thanks @CodeCaster. Always good seeing high-rep users looking for duplicates rather than eking out a few more points answering them. – Rawling Nov 02 '16 at 13:14

1 Answers1

19

In case of large test collection (when Count() is expensive) you can try a typical trick:

if (test.Skip(1).Any()) 

In general case test.Count() > x can be rewritten into

if (test.Skip(x).Any()) 

Edit: you may want to hide such a tricks in a method, say EnsureCount:

  public static partial class EnumerableExtensions {
    public static IEnumerable<T> EnsureCount<T>(this IEnumerable<T> source, int count) {
      if (null == source)
        throw new ArgumentNullException("source");

      if (count <= 0)
        foreach (var item in source)
          yield return item;
      else {
        List<T> buffer = new List<T>(count);

        foreach (var item in source) {
          if (buffer == null)
            yield return item;
          else {
            buffer.Add(item);

            if (buffer.Count >= count) {
              foreach (var x in buffer)
                yield return x;

              buffer = null;
            }
          }
        }
      }
    }
  } 

and so your code will be

  var test = Test()
    .Where(o => o > 2 && o < 6)
    .EnsureCount(2); // <- Count() > 1, so at least 2 items

  foreach (var t in test)
    Console.WriteLine(t); 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215