Вопрос по .net, linq, performance, c# – Существует ли расширение LINQ или (разумный / эффективный набор расширений LINQ), определяющее, имеет ли коллекция хотя бы элементы «x»?

8

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

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

bool collectionHasAtLeast2Items = collection.AtLeast(2);

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

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

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

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

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

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

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

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

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

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

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

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

@HansKesting - это, вероятно, говорилось, пока адвокаты не добрались до него :) Mark Schultheiss
@HansKesting: Да, возможно, вы правы, спасибо. nicodemus13
Почему бы не создать коллекцию, загрузить некоторые данные и протестировать? paparazzo
Кстати,Take(n).Count() не будет перечислять дважды:Take метод лениво передает свои результаты, поэтому самое необходимое - один проходn Предметы. LukeH
На странице, на которую вы ссылаетесь, есть замечаниеTake<TSource> enumerates source and yields elements until count elements have been yielded or source contains no more elements. это может быть прочитано как "если исходный список короче, чем счетчик, то возвращается только этот короткий список". Hans Kesting

Ваш Ответ

3   ответа
3

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.

4
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;
    }
}
@abatishchev, да, может, но издержки меньше, если вы манипулируете счетчиком напрямую (да, это микрооптимизация ...)
Тогда весь код может быть написан вообще без LINQ, это будет немного быстрее, но может возникнуть проблема с авторскими правами :)
Спасибо, это хороший ручной подход, хотя я думаю, что я пойду сSkip(n).Any()Я предпочитаю использовать встроенное поведение там, где могу, и это наиболее ясно показывает мое намерение - я думаю.Take() кажется быстрее чемSkip() однако, но это кажется микрооптимизацией. nicodemus13
Код для общего случая можно обернуть в метод LINQ, не так ли?
Размышляя оCount() код, который очень похож на ваш, он также добавляет в «проверено» заявление по всемуn++ часть. Также, вероятно, должен быть охранник (count & gt; = 0). nicodemus13
4

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

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

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

@ThomasLevesque: согласитесь, ответ Кендалла раскрывает это лучше.
Я думаю, что он имеет в виду метод с более высокой производительностью, отличный от Count ().
Я думаю, что это хороший метод, учитывая ограниченияIEnumerable лучшее, на что вы можете надеяться в самом общем случае, это то, что вы перечисляете только два элемента. Получение оптимизации дляICollection «бесплатно» хорошая победа
@abatishchev, это O (1), если коллекция реализует ICollection, но не в общем случае. В некоторых случаях может быть дорогим выполнить полный подсчет, когда вам нужно только проверить, есть ли хотя бы 2 элемента ...
@ericosg: мое исследование показывает производительность O (1).

Похожие вопросы