Digamos que eu tenho uma sequência.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Obter a sequência não é barato e é gerado dinamicamente, e quero iterá-la apenas uma vez.
Quero obter 0 - 999999 (ou seja, tudo, menos o último elemento)
Reconheço que eu poderia fazer algo como:
sequence.Take(sequence.Count() - 1);
mas isso resulta em duas enumerações na grande sequência.
Existe uma construção LINQ que me permite fazer:
sequence.TakeAllButTheLastElement();
sequenceList.RemoveAt(sequence.Count - 1);
. No meu caso, é aceitável porque, depois de todas as manipulações do LINQ, tenho que convertê-lo em array ou deIReadOnlyCollection
qualquer maneira. Gostaria de saber qual é o seu caso de uso em que você nem considera o cache? Como posso ver, mesmo a resposta aprovada faz algum armazenamento em cache tão simples de converterList
é muito mais fácil e mais curto na minha opinião.Respostas:
Não conheço uma solução Linq - mas você pode codificar facilmente o algoritmo usando geradores (retorno de rendimento).
Ou como uma solução generalizada descartando os últimos n itens (usando uma fila como sugerido nos comentários):
fonte
Como uma alternativa para criar seu próprio método e, em um caso, a ordem dos elementos não for importante, o próximo funcionará:
fonte
equence.Reverse().Skip(1).Reverse()
não uma boa soluçãoPorque eu não sou fã de usar explicitamente um
Enumerator
, aqui está uma alternativa. Observe que os métodos do wrapper são necessários para permitir que argumentos inválidos sejam lançados mais cedo, em vez de adiar as verificações até que a sequência seja realmente enumerada.De acordo com a sugestão de Eric Lippert, generaliza facilmente n itens:
Onde agora eu buffer antes de render em vez de depois de render, para que o
n == 0
caso não precise de tratamento especial.fonte
buffered=false
uma cláusula else antes de atribuirbuffer
. A condição já está sendo verificada de qualquer maneira, mas isso evitaria a configuração redundante debuffered
cada vez no loop.DropLast
. Caso contrário, a validação ocorrerá somente quando você realmente enumerar a sequência (na primeira chamada ouMoveNext
na resultanteIEnumerator
). Não é uma coisa divertida para depurar quando pode haver uma quantidade arbitrária de código entre a geraçãoIEnumerable
e a enumeração. Hoje em dia eu escreveriaInternalDropLast
como uma função interna deDropLast
, mas essa funcionalidade não existia em C # quando escrevi esse código há 9 anos.O
Enumerable.SkipLast(IEnumerable<TSource>, Int32)
método foi adicionado no .NET Standard 2.1. Faz exatamente o que você quer.De https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
fonte
Nada no BCL (ou MoreLinq eu acredito), mas você pode criar seu próprio método de extensão.
fonte
if (!first)
efirst = false
sair do if.prev
na primeira iteração e girar para sempre depois disso'.using
declaração agora.Seria útil se o .NET Framework fosse fornecido com um método de extensão como este.
fonte
Uma pequena expansão na solução elegante de Joren:
Onde o shrink implementa uma contagem simples para a frente para descartar os primeiros
left
muitos elementos e o mesmo buffer descartado para descartar os últimosright
muitos elementos.fonte
se você não tiver tempo para lançar sua própria extensão, eis uma maneira mais rápida:
fonte
Uma pequena variação na resposta aceita, que (para o meu gosto) é um pouco mais simples:
fonte
Se você pode obter o
Count
ouLength
de um enumerável, o que, na maioria dos casos, é possível, bastaTake(n - 1)
Exemplo com matrizes
Exemplo com
IEnumerable<T>
fonte
Por que não apenas
.ToList<type>()
na sequência, então conte e atenda a chamada como você fez originalmente ... mas, como foi inserida em uma lista, ela não deve fazer uma enumeração cara duas vezes. Certo?fonte
A solução que eu uso para esse problema é um pouco mais elaborada.
Minha classe estática util contém um método de extensão
MarkEnd
que converte osT
itens emEndMarkedItem<T>
itens. Cada elemento é marcado com um extraint
, que é 0 ; ou (caso um esteja particularmente interessado nos últimos 3 itens) -3 , -2 ou -1 nos últimos 3 itens.Isso pode ser útil por si só, por exemplo , quando você deseja criar uma lista em um
foreach
loop simples com vírgulas após cada elemento, exceto os últimos 2, com o penúltimo item seguido por uma palavra conjunta (como " e " ou " ou ") e o último elemento seguido de um ponto.Para gerar a lista inteira sem os últimos n itens, o método de extensão
ButLast
simplesmente itera ao longo dosEndMarkedItem<T>
segundosEndMark == 0
.Se você não especificar
tailLength
, apenas o último item será marcado (inMarkEnd()
) ou descartado (inButLast()
).Como as outras soluções, isso funciona em buffer.
fonte
fonte
Eu não acho que pode ser mais sucinto do que isso - também garantindo Dispose the
IEnumerator<T>
:Edit: tecnicamente idêntico a esta resposta .
fonte
Com o C # 8.0, você pode usar intervalos e índices para isso.
Por padrão, o C # 8.0 requer o .NET Core 3.0 ou o .NET Standard 2.1 (ou superior). Marque esta discussão para usar com implementações mais antigas.
fonte
Você pode escrever:
fonte
Esta é uma solução geral e elegante do IMHO que manipulará todos os casos corretamente:
fonte
Minha
IEnumerable
abordagem tradicional :fonte
Uma maneira simples seria apenas converter para uma fila e desenfileirar até restar apenas o número de itens que você deseja pular.
fonte
Poderia ser:
Eu acho que deveria ser como "Onde", mas preservando a ordem (?).
fonte
Se a velocidade é um requisito, essa maneira antiga deve ser a mais rápida, mesmo que o código não pareça tão suave quanto o linq poderia.
Isso requer que a sequência seja uma matriz, pois possui um comprimento fixo e itens indexados.
fonte
Eu provavelmente faria algo assim:
Esta é uma iteração com uma verificação de que não é a última de cada vez.
fonte