Допустим, у меня есть последовательность.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Получение последовательности стоит недешево и генерируется динамически, и я хочу повторить ее только один раз.
Я хочу получить 0 - 999999 (т.е. все, кроме последнего элемента)
Я понимаю, что могу сделать что-то вроде:
sequence.Take(sequence.Count() - 1);
но это приводит к двум перечислениям по большой последовательности.
Есть ли конструкция LINQ, которая позволяет мне:
sequence.TakeAllButTheLastElement();
sequenceList.RemoveAt(sequence.Count - 1);
. В моем случае это приемлемо, потому что после всех манипуляций с LINQ мне нужно преобразовать его в массив или вIReadOnlyCollection
любом случае. Интересно, какой у вас вариант использования, когда вы даже не рассматриваете кеширование? Как я вижу, даже утвержденный ответ делает некоторое кеширование, поэтому простое преобразование, наList
мой взгляд, намного проще и короче.Ответы:
Я не знаю решения Linq - но вы можете легко запрограммировать алгоритм самостоятельно, используя генераторы (доходность).
Или как обобщенное решение, отбрасывающее последние n элементов (с использованием очереди, как предложено в комментариях):
источник
В качестве альтернативы созданию собственного метода и в случае, если порядок элементов не важен, будет работать следующий:
источник
equence.Reverse().Skip(1).Reverse()
не очень хорошее решениеПоскольку я не поклонник явного использования
Enumerator
, вот альтернатива. Обратите внимание, что методы-оболочки необходимы для того, чтобы позволить недопустимым аргументам вызывать раньше, а не откладывать проверки до тех пор, пока последовательность не будет фактически перечислена.Согласно предложению Эрика Липперта, его легко обобщить на n элементов:
Где я теперь буферизую перед уступкой, а не после уступки, так что
n == 0
случай не требует специальной обработки.источник
buffered=false
в предложении else перед назначениемbuffer
. В любом случае условие уже проверяется, но это позволит избежать повторной установкиbuffered
каждый раз в цикле.DropLast
. В противном случае проверка происходит только тогда, когда вы фактически перечисляете последовательность (при первом вызовеMoveNext
полученногоIEnumerator
). Неинтересно отлаживать, когда между генерациейIEnumerable
и фактическим перечислением может быть произвольный объем кода . В настоящее время я бы написалInternalDropLast
как внутреннюю функциюDropLast
, но этой функции не было в C #, когда я писал этот код 9 лет назад.Enumerable.SkipLast(IEnumerable<TSource>, Int32)
Метод был добавлен в .NET 2.1 стандарта. Он делает именно то, что вы хотите.С https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
источник
Ничего в BCL (или, как мне кажется, в MoreLinq), но вы можете создать свой собственный метод расширения.
источник
if (!first)
и вытащитьfirst = false
из if.prev
в первой итерации, а после этого цикл навсегда».using
заявление сейчас.Было бы полезно, если бы .NET Framework поставлялась с таким методом расширения.
источник
Небольшое расширение элегантного решения Йорена:
Где shrink реализует простой счетчик вперед для отбрасывания первых
left
элементов и тот же отброшенный буфер для отбрасывания последнихright
элементов.источник
Если у вас нет времени на развертывание собственного расширения, вот более быстрый способ:
источник
Небольшое изменение принятого ответа, которое (на мой вкус) немного проще:
источник
Если вы можете получить
Count
илиLength
перечислимого, что в большинстве случаев возможно, тогда простоTake(n - 1)
Пример с массивами
Пример с
IEnumerable<T>
источник
Почему бы не просто
.ToList<type>()
в последовательности, а затем подсчитать вызовы и взять, как вы это делали изначально ... но поскольку он был помещен в список, он не должен выполнять дорогостоящее перечисление дважды. Правильно?источник
Решение, которое я использую для этой проблемы, немного сложнее.
Мой статический класс util содержит метод расширения,
MarkEnd
который преобразуетT
-items вEndMarkedItem<T>
-items. Каждый элемент отмечен дополнительным значениемint
, равным либо 0 ; или (если вас особенно интересуют последние 3 пункта) -3 , -2 или -1 для последних 3 элементов.Это может быть полезно само по себе, например, когда вы хотите создать список в простом
foreach
цикле с запятыми после каждого элемента, кроме последних 2, с предпоследним элементом, за которым следует слово-союз (например, « и » или « или »), и последний элемент, за которым следует точка.Для создания всего списка без последних n элементов метод расширения
ButLast
просто выполняет итерацию поEndMarkedItem<T>
s whileEndMark == 0
.Если вы не укажете
tailLength
, помечается (вMarkEnd()
) или отбрасывается (вButLast()
) только последний элемент .Как и другие решения, это работает путем буферизации.
источник
источник
Я не думаю, что это может быть более лаконично, чем это - также гарантируя удаление
IEnumerator<T>
:Изменить: технически идентично этому ответу .
источник
В C # 8.0 для этого можно использовать диапазоны и индексы .
По умолчанию для C # 8.0 требуется .NET Core 3.0 или .NET Standard 2.1 (или выше). Отметьте этот поток, чтобы использовать его со старыми реализациями.
источник
Вы могли бы написать:
источник
Это общее и элегантное решение IMHO, которое будет правильно обрабатывать все случаи:
источник
Мой традиционный
IEnumerable
подход:источник
Простой способ - просто преобразовать в очередь и исключить из нее, пока не останется только количество элементов, которые вы хотите пропустить.
источник
Может быть:
Думаю, это должно быть как "Где", но с сохранением порядка (?).
источник
Если скорость является требованием, этот старый способ должен быть самым быстрым, даже если код выглядит не так гладко, как это могло бы сделать linq.
Для этого требуется, чтобы последовательность была массивом, поскольку она имеет фиксированную длину и индексированные элементы.
источник
Я бы, наверное, сделал что-то вроде этого:
Это одна итерация с проверкой, что она не последняя для каждого раза.
источник