Dada uma coleção, existe uma maneira de obter os últimos N elementos dessa coleção? Se não houver um método na estrutura, qual seria a melhor maneira de escrever um método de extensão para fazer isso?
collection.Skip(Math.Max(0, collection.Count() - N));
Essa abordagem preserva a ordem dos itens sem depender de nenhuma classificação e possui ampla compatibilidade entre vários provedores LINQ.
É importante tomar cuidado para não ligar Skip
com um número negativo. Alguns provedores, como o Entity Framework, produzirão uma ArgumentException quando apresentados com um argumento negativo. A chamada para Math.Max
evita isso ordenadamente.
A classe abaixo possui todos os elementos essenciais para os métodos de extensão, que são: uma classe estática, um método estático e o uso da this
palavra - chave.
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));
}
}
Uma breve nota sobre o desempenho:
Como a chamada para Count()
pode causar a enumeração de determinadas estruturas de dados, essa abordagem corre o risco de causar duas passagens nos dados. Isso não é realmente um problema com a maioria dos enumeráveis; de fato, já existem otimizações para listas, matrizes e até consultas EF para avaliar a Count()
operação no tempo O (1).
Se, no entanto, você deve usar um enumerável de encaminhamento somente e gostaria de evitar duas passagens, considere um algoritmo de uma passagem como Lasse V. Karlsen ou Mark Byers descrevem. Ambas as abordagens usam um buffer temporário para armazenar itens enquanto enumera, que são gerados quando o final da coleção é encontrado.
List
es,LinkedList
a solução de James tende a ser mais rápida, embora não por uma ordem de magnitude. Se o IEnumerable for calculado (via Enumerable.Range, por exemplo), a solução de James levará mais tempo. Não consigo pensar em nenhuma maneira de garantir uma única passagem sem saber algo sobre a implementação ou copiar valores para uma estrutura de dados diferente.UPDATE: Para resolver o problema de clintp: a) O uso do método TakeLast () que eu defini acima resolve o problema, mas se você realmente deseja fazê-lo sem o método extra, basta reconhecer que, enquanto Enumerable.Reverse () pode ser usado como um método de extensão, não é necessário usá-lo dessa maneira:
fonte
List<string> mystring = new List<string>() { "one", "two", "three" }; mystring = mystring.Reverse().Take(2).Reverse();
Eu recebo um erro do compilador porque .Reverse () retorna nulo e o compilador escolhe esse método em vez do Linq que retorna um IEnumerable. Sugestões?N
registros, pode pular o segundoReverse
.Nota : Perdi o título da sua pergunta que dizia Usando o Linq , então minha resposta não usa o Linq.
Se você deseja evitar o armazenamento em cache de uma cópia não preguiçosa de toda a coleção, escreva um método simples que faça isso usando uma lista vinculada.
O método a seguir adiciona cada valor encontrado na coleção original a uma lista vinculada e reduz a lista vinculada ao número de itens necessários. Como mantém a lista vinculada aparada para esse número de itens o tempo todo durante a iteração na coleção, ela manterá apenas uma cópia de no máximo N itens da coleção original.
Não requer que você saiba o número de itens na coleção original, nem repita mais de uma vez.
Uso:
Método de extensão:
fonte
Aqui está um método que funciona em qualquer enumerável, mas usa apenas armazenamento temporário O (N):
Uso:
Ele funciona usando um buffer de anel do tamanho N para armazenar os elementos como os vê, substituindo elementos antigos por novos. Quando o final do enumerável é alcançado, o buffer do anel contém os últimos N elementos.
fonte
n
.O .NET Core 2.0+ fornece o método LINQ
TakeLast()
:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.takelast
exemplo :
fonte
netcoreapp1.x
), mas apenas para as v2.0 e v2.1 de dotnetcore (netcoreapp2.x
). É possível que você esteja direcionando a estrutura completa (por exemplonet472
), que também não é suportada. (as bibliotecas padrão .net podem ser usadas por qualquer uma das opções acima, mas podem apenas expor determinadas APIs específicas a uma estrutura de destino. consulte docs.microsoft.com/en-us/dotnet/standard/frameworks )Estou surpreso que ninguém tenha mencionado, mas SkipWhile tem um método que usa o índice do elemento .
O único benefício perceptível que esta solução apresenta sobre outras é que você pode ter a opção de adicionar um predicado para fazer uma consulta LINQ mais poderosa e eficiente, em vez de ter duas operações separadas que percorrem o IEnumerable duas vezes.
fonte
Use EnumerableEx.TakeLast no assembly System.Interactive do RX. É uma implementação O (N) como a de @ Mark, mas usa uma fila em vez de uma construção de buffer de anel (e remove da fila os itens quando atinge a capacidade do buffer).
(Nota: esta é a versão IEnumerable - não a versão IObservable, embora a implementação das duas seja praticamente idêntica)
fonte
Queue<T>
implementado usando um buffer circular ?Se você estiver lidando com uma coleção com uma chave (por exemplo, entradas de um banco de dados), uma solução rápida (ou seja, mais rápida que a resposta selecionada) seria
fonte
Se você não se importa de mergulhar no Rx como parte da mônada, pode usar
TakeLast
:fonte
Se o uso de uma biblioteca de terceiros for uma opção, o MoreLinq define o
TakeLast()
que faz exatamente isso.fonte
Tentei combinar eficiência e simplicidade e acabei com isso:
Sobre desempenho: Em C #,
Queue<T>
é implementado usando um buffer circular para que não haja instanciação de objeto feita a cada loop (somente quando a fila está crescendo). Não configurei a capacidade da fila (usando o construtor dedicado) porque alguém pode chamar esse ramal comcount = int.MaxValue
. Para um desempenho extra, você pode verificar se o código-fonte implementaIList<T>
e, se sim, extrair diretamente os últimos valores usando índices de matriz.fonte
É um pouco ineficiente obter o último N de uma coleção usando o LINQ, pois todas as soluções acima exigem iteração na coleção.
TakeLast(int n)
noSystem.Interactive
também tem este problema.Se você tem uma lista, uma coisa mais eficiente a fazer é cortá-la usando o seguinte método
com
e alguns casos de teste
fonte
Eu sei que é tarde demais para responder a essa pergunta. Mas se você estiver trabalhando com uma coleção do tipo IList <> e não se importar com uma ordem da coleção retornada, esse método estará funcionando mais rapidamente. Eu usei a resposta de Mark Byers e fiz algumas alterações. Então agora o método TakeLast é:
Para o teste, usei o método Mark Byers e a resposta de kbrimington . Este é o teste:
E aqui estão os resultados para obter 10 elementos:
e para obter 1000001 elementos, os resultados são:
fonte
Aqui está a minha solução:
O código é um pouco robusto, mas como um componente reutilizável, ele deve ter o melhor desempenho possível na maioria dos cenários e manterá o código que o está usando agradável e conciso. :-)
Meu
TakeLast
para nãoIList`1
é baseado no mesmo algoritmo de buffer de anel que o encontrado nas respostas de @Mark Byers e @MackieChan. É interessante como eles são semelhantes - escrevi o meu de forma totalmente independente. Acho que há realmente apenas uma maneira de fazer um buffer de anel corretamente. :-)Olhando para a resposta de @ kbrimington, uma verificação adicional pode ser adicionada a isso para
IQuerable<T>
retornar à abordagem que funciona bem com o Entity Framework - assumindo que o que tenho neste momento não funciona.fonte
Abaixo do exemplo real, como obter os três últimos elementos de uma coleção (matriz):
fonte
Usando este método para obter todo o intervalo sem erro
fonte
Implementação pouco diferente com o uso de buffer circular. Os benchmarks mostram que o método é cerca de duas vezes mais rápido que os que usam o Queue (implementação do TakeLast no System.Linq ), mas não sem um custo - ele precisa de um buffer que cresça junto com o número solicitado de elementos, mesmo se você tiver um pequena coleção, você pode obter uma enorme alocação de memória.
fonte