Подтвердить что ты не робот

Как обеспечить, чтобы последовательность имела определенную длину?

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

bool hasOneElement = seq.Count() == 1

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

4b9b3361

Ответ 1

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

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 работал надлежащим образом, просто противно... в то время как много веселья;)

EDIT: Я только что видел, что вы придумали точно такую ​​же вещь, но для произвольной длины. Ваше окончательное выражение о возврате неверно, но должно быть возвращено !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();
    }
}

EDIT: И теперь для функциональных поклонников рекурсивная форма 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);
}

EDIT: обратите внимание, что для чего-то вроде LINQ to SQL вы должны использовать простой подход Count(), потому что это позволит сделать это в базе данных, а не после получения фактических результатов.

Ответ 2

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

 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.

Ответ 3

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

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

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

Ответ 4

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

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.