Как мне убедиться, что последовательность имеет определенную длину? - PullRequest
22 голосов
/ 27 сентября 2010

Я хочу проверить, что IEnumerable содержит точно один элемент. Этот фрагмент работает:

bool hasOneElement = seq.Count() == 1

Однако это не очень эффективно, так как Count() будет перечислять весь список. Очевидно, что знание того, что список пуст или содержит более 1 элемента, означает, что он не пуст. Есть ли метод расширения, который имеет такое короткое замыкание?

Ответы [ 5 ]

20 голосов
/ 27 сентября 2010

Это должно сделать это:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        // Check we've got at least one item
        if (!iterator.MoveNext())
        {
            return false;
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

Вы могли бы исключить это дальше, но я не советую вам делать это:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return iterator.MoveNext() && !iterator.MoveNext();
    }
}

ЭтоТакая хитрая хитрость, но, вероятно, не должна использоваться в производственном коде.Это просто недостаточно ясно.Тот факт, что побочный эффект в LHS оператора && необходим для правильной работы RHS, просто мерзок ... хотя это очень весело;)

РЕДАКТИРОВАТЬ: я только что видел, что выпридумал точно то же самое, но на произвольную длину.Ваше окончательное заявление о возврате неверно - оно должно быть возвращено !en.MoveNext().Вот полный метод с более хорошим именем (IMO), проверка аргументов и оптимизация для ICollection / ICollection<T>:

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count",
                                              "count must not be negative");
    }
    // We don't rely on the optimizations in LINQ to Objects here, as
    // they have changed between versions.
    ICollection<T> genericCollection = source as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == count;
    }
    ICollection nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == count;
    }
    // Okay, we're finally ready to do the actual work...
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        for (int i = 0; i < count; i++)
        {
            if (!iterator.MoveNext())
            {
                return false;
            }
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

РЕДАКТИРОВАТЬ: И теперь для функциональных вентиляторов, рекурсивная форма CountEquals ( пожалуйста, не используйте это , это только здесь для смешков):

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", 
                                              "count must not be negative");
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return IteratorCountEquals(iterator, count);
    }
}

private static bool IteratorCountEquals<T>(IEnumerator<T> iterator, int count)
{
    return count == 0 ? !iterator.MoveNext()
        : iterator.MoveNext() && IteratorCountEquals(iterator, count - 1);
}

РЕДАКТИРОВАТЬ: Обратите внимание, что для чего-то вроде LINQ to SQL, вы должны использовать простой подход Count() -потому что это позволит сделать это в базе данных, а не после получения фактических результатов.

7 голосов
/ 27 сентября 2010

Нет, но вы можете написать один самостоятельно:

 public static bool HasExactly<T>(this IEnumerable<T> source, int count)
 {
   if(source == null)
      throw new ArgumentNullException("source");

   if(count < 0)
      return false;

   return source.Take(count + 1).Count() == count;
 }

РЕДАКТИРОВАТЬ: изменено с по крайней мере на точно после уточнения.

Для более общего и эффективного решения (которое использует только 1 перечислитель и проверяет, реализует ли последовательность ICollection или ICollection<T>, в этом случае перечисление не требуется), вы можете взглянуть на мой ответ здесь , который позволяет вам указать, ищете ли вы Exact, AtLeast или AtMost тесты.

4 голосов
/ 27 сентября 2010

seq.Skip(1).Any() скажет вам, есть ли в списке ноль или один элемент.

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

    public static bool LengthEquals<T>(this IEnumerable<T> en, int length)
    {
        using (var er = en.GetEnumerator())
        {
            for (int i = 0; i < length; i++)
            {
                if (!er.MoveNext())
                    return false;
            }
            return !er.MoveNext();
        }
    }
1 голос
/ 27 сентября 2010

Как насчет этого?

public static bool CountEquals<T>(this IEnumerable<T> source, int count) {
    return source.Take(count + 1).Count() == count;
}

Take() гарантирует, что мы никогда не позвоним MoveNext более count+1 раз.

Я хотел бы отметить, что длялюбой экземпляр ICollection, оригинальная реализация source.Count() == count должна быть быстрее, потому что Count() оптимизирован, чтобы просто посмотреть на элемент Count.

0 голосов
/ 27 сентября 2010

Я верю, что вы ищете .Single().Все, кроме одного, вызовет исключение InvalidOperationException, которое вы можете перехватить.

http://msdn.microsoft.com/nb-no/library/bb155325.aspx

...