Есть ли расширение LINQ или (разумный / эффективный набор расширений LINQ), которые определяют, имеет ли коллекция хотя бы элементы "x"?

У меня есть код, который должен знать, что коллекция не должна быть пустой или содержать только один элемент.

В общем, я хочу расширение формы:

bool collectionHasAtLeast2Items = collection.AtLeast(2);

Я могу легко написать расширение, перечисляя коллекцию и увеличивая индексатор, пока не достигну запрошенного размера или не исчерпаю элементы, но есть ли уже что-то в инфраструктуре LINQ, которая могла бы сделать это? Мои мысли (в порядке того, что пришло ко мне):

bool collectionHasAtLeast2Items = collection.Take(2).Count() == 2; или же

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

Казалось бы, это работает, хотя поведение взятия большего количества элементов, чем содержит коллекция, не определено (в документации). Метод Enumerable.Take, однако, похоже, делает то, что и следовало ожидать.

Это не самое эффективное решение: либо один раз перечислить, чтобы взять элементы, затем пересчитать снова, чтобы подсчитать их, что не нужно, либо перечислить один раз, чтобы взять элементы, затем построить список, чтобы получить свойство count, которое не является перечислителем. -я, как я на самом деле не хочу список.

Это не красиво, так как мне всегда приходится делать два утверждения: сначала взять "х", а затем проверить, что я действительно получил "х", и это зависит от недокументированного поведения.

Или, возможно, я мог бы использовать:

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

Однако это не семантически ясно. Возможно, лучше всего обернуть это именем метода, которое означает то, что я хочу. Я предполагаю, что это будет эффективно, я не размышлял над кодом.

Некоторые другие мысли используют Last(), но я явно не хочу перечислять всю коллекцию.

Или, может быть Skip(2).Any()опять не семантически не совсем очевиден, но лучше чем ElementAtOrDefault(2) != nullХотя я думаю, что они дают одинаковый результат?

Какие-нибудь мысли?

3 ответа

Решение
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;
    }
}

Ты можешь использовать Count() >= 2, если вы последовательность реализует ICollection?


За кулисами, Enumerable.Count() метод расширения проверяет, реализует ли последовательность под циклом ICollection, Если это действительно так, Count возвращаемое свойство, поэтому целевая производительность должна быть O(1).

таким образом ((IEnumerable<T>)((ICollection)sequence)).Count() >= x также должен иметь O(1).

Вы могли бы использовать Count, но если производительность является проблемой, вам будет лучше с Take,

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

поскольку Take (Я полагаю) использует отложенное выполнение, оно будет проходить через коллекцию только один раз.

Абатищев отметил, что Count O(1) с ICollectionТаким образом, вы можете сделать что-то вроде этого и получить лучшее из обоих миров.

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

Вы также можете использовать Skip/AnyБьюсь об заклад, это будет даже быстрее, чем Take/Count,

Другие вопросы по тегам