Для данной коллекции есть ли способ получить последние N элементов этой коллекции? Если в структуре нет метода, как лучше написать метод расширения для этого?
collection.Skip(Math.Max(0, collection.Count() - N));
Этот подход сохраняет порядок элементов без зависимости от какой-либо сортировки и обладает широкой совместимостью между несколькими поставщиками LINQ.
Важно соблюдать осторожность, чтобы не звонить Skip
с отрицательным номером. Некоторые провайдеры, такие как Entity Framework, будут генерировать ArgumentException, когда представлены с отрицательным аргументом. Призыв Math.Max
избегать этого аккуратно.
В приведенном ниже классе есть все необходимое для методов расширения: статический класс, статический метод и использование this
ключевого слова.
public static class MiscExtensions
{
// Ex: collection.TakeLast(5);
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)
{
return source.Skip(Math.Max(0, source.Count() - N));
}
}
Краткая заметка о производительности:
Поскольку обращение к Count()
может вызвать перечисление определенных структур данных, такой подход может вызвать два прохода по данным. На самом деле это не проблема для большинства перечислимых; фактически уже существуют оптимизации для списков, массивов и даже запросов EF для оценки Count()
операции за O (1) времени.
Однако, если вы должны использовать перечислимое только для пересылки и хотели бы избежать двухпроходного прохождения, рассмотрите однопроходный алгоритм, как описывают Лассе В. Карлсен или Марк Байерс . Оба этих подхода используют временный буфер для хранения элементов при перечислении, которые выдаются после того, как будет найден конец коллекции.
List
s иLinkedList
s, решение Джеймса имеет тенденцию быть быстрее, хотя и не на порядок. Если рассчитывается IEnumerable (например, через Enumerable.Range), решение Джеймса занимает больше времени. Я не могу придумать способа гарантировать один проход, не зная ничего о реализации или не копируя значения в другую структуру данных.ОБНОВЛЕНИЕ: Для решения проблемы clintp: a) Использование метода TakeLast (), который я определил выше, решает проблему, но если вы действительно хотите сделать это без дополнительного метода, то вам просто нужно признать, что Enumerable.Reverse () может быть Если вы используете метод расширения, вы не обязаны использовать его таким образом:
источник
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
я получаю ошибку компилятора, потому что .Reverse () возвращает void, и компилятор выбирает этот метод вместо метода Linq, который возвращает IEnumerable. Предложения?N
записи, вы можете пропустить вторуюReverse
.Примечание : я пропустил заголовок вашего вопроса, который гласил « Использование Linq» , поэтому в моем ответе фактически не используется Linq.
Если вы хотите избежать кэширования неленивой копии всей коллекции, вы можете написать простой метод, который делает это, используя связанный список.
Следующий метод добавит каждое значение, которое он найдет в исходной коллекции, в связанный список и урежет связанный список до необходимого количества элементов. Поскольку связанный список обрезается до этого количества элементов все время путем перебора всей коллекции, он будет хранить только копию не более N элементов из исходной коллекции.
Он не требует, чтобы вы знали количество элементов в исходной коллекции и не повторяли ее более одного раза.
Использование:
Метод расширения:
источник
Вот метод, который работает с любым перечислимым, но использует только O (N) временное хранилище:
Использование:
Он работает с использованием кольцевого буфера размера N для хранения элементов так, как он их видит, перезаписывая старые элементы новыми. Когда достигнут конец перечислимого, кольцевой буфер содержит последние N элементов.
источник
n
..NET Core 2.0+ предоставляет метод LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
пример :
источник
netcoreapp1.x
), но только для v2.0 и v2.1 функции dotnetcore (netcoreapp2.x
). Возможно, вы нацелены на полную структуру (напримерnet472
), которая также не поддерживается. (Стандартные библиотеки .net могут использоваться любым из вышеперечисленных, но могут предоставлять только определенные API-интерфейсы, специфичные для целевой платформы. См. docs.microsoft.com/en-us/dotnet/standard/frameworks )Я удивлен, что никто не упомянул об этом, но у SkipWhile есть метод, который использует индекс элемента .
Единственное ощутимое преимущество, которое дает это решение перед другими, состоит в том, что вы можете иметь возможность добавить предикат для создания более мощного и эффективного запроса LINQ вместо двух отдельных операций, которые дважды пересекают IEnumerable.
источник
Используйте EnumerableEx.TakeLast в сборке System.Interactive RX. Это реализация O (N), как у @ Mark, но она использует очередь, а не конструкцию кольцевого буфера (и удаляет элементы, когда она достигает емкости буфера).
(Примечание: это версия IEnumerable, а не версия IObservable, хотя реализация этих двух программ практически идентична)
источник
Queue<T>
реализован с использованием циклического буфера ?Если вы имеете дело с коллекцией с ключом (например, записи из базы данных), быстрое (т.е. быстрее, чем выбранный ответ) решение будет
источник
Если вы не против погрузиться в Rx как часть монады, вы можете использовать
TakeLast
:источник
Если использование сторонней библиотеки является опцией, MoreLinq определяет,
TakeLast()
что именно это и делает.источник
Я попытался объединить эффективность и простоту и в конечном итоге с этим:
О производительности: в C #
Queue<T>
реализован с использованием циклического буфера, поэтому в каждом цикле не выполняется создание объектов (только когда очередь растет). Я не установил емкость очереди (используя выделенный конструктор), потому что кто-то может вызвать это расширение с помощьюcount = int.MaxValue
. Для дополнительной производительности вы можете проверить, реализует ли источник,IList<T>
и если да, напрямую извлечь последние значения, используя индексы массива.источник
Немного неэффективно брать последние N из коллекции, используя LINQ, поскольку все вышеупомянутые решения требуют итерации по всей коллекции.
TakeLast(int n)
ВSystem.Interactive
тоже есть эта проблема.Если у вас есть список, более эффективный способ - нарезать его, используя следующий метод
с участием
и некоторые тесты
источник
Я знаю, что уже поздно отвечать на этот вопрос. Но если вы работаете с коллекцией типа IList <> и вам не важен порядок возвращаемой коллекции, тогда этот метод работает быстрее. Я использовал ответ Марка Байерса и внес небольшие изменения. Так что теперь метод TakeLast:
Для теста я использовал метод Марка Байерса и ответ Кбримингтона . Это тест:
И вот результаты для взятия 10 элементов:
и для взятия 1000001 элементов получаются следующие результаты:
источник
Вот мое решение:
Код немного коренастый, но как вставляемый повторно используемый компонент, он должен работать так же хорошо, как и в большинстве сценариев, и он будет сохранять код, который его использует, красивым и лаконичным. :-)
Мой
TakeLast
для неIList`1
основан на том же алгоритме кольцевого буфера, что и в ответах по @Mark Байерс и @MackieChan дальше. Интересно, насколько они похожи - я написал абсолютно самостоятельно. Думаю, на самом деле есть только один способ правильно создать кольцевой буфер. :-)Глядя на ответ @ kbrimington, можно добавить дополнительную проверку,
IQuerable<T>
чтобы вернуться к подходу, который хорошо работает с Entity Framework - при условии, что то, что у меня есть на данный момент, не так.источник
Ниже приведен реальный пример того, как взять последние 3 элемента из коллекции (массива):
источник
Используя этот метод, чтобы получить весь диапазон без ошибок
источник
Немного отличается реализация с использованием кольцевого буфера. Тесты показывают, что метод примерно в два раза быстрее, чем те, которые используют Queue (реализация TakeLast в System.Linq ), однако не без затрат - ему нужен буфер, который увеличивается вместе с запрошенным количеством элементов, даже если у вас есть Небольшая коллекция, вы можете получить огромное выделение памяти.
источник