Existe alguma maneira de separar um List<SomeObject>
em várias listas separadas SomeObject
, usando o índice do item como o delimitador de cada divisão?
Deixe-me exemplificar:
Eu tenho um List<SomeObject>
e preciso de um List<List<SomeObject>>
ou List<SomeObject>[]
, para que cada uma dessas listas resultantes contenha um grupo de 3 itens da lista original (sequencialmente).
por exemplo.:
Lista original:
[a, g, e, w, p, s, q, f, x, y, i, m, c]
Listas resultantes:
[a, g, e], [w, p, s], [q, f, x], [y, i, m], [c]
Eu também precisaria do tamanho da lista resultante para ser um parâmetro dessa função.
fonte
[a,g,e]
antes de enumerar mais a lista original.GroupBy(x=>f(x)).First()
nunca produzirá um grupo. O OP perguntou sobre listas, mas se escrevermos para trabalhar com o IEnumerable, fazendo apenas uma única iteração, colheremos a vantagem de desempenho.Essa pergunta é um pouco antiga, mas acabei de escrever isso e acho que é um pouco mais elegante que as outras soluções propostas:
fonte
if (chunksize <= 0) throw new ArgumentException("Chunk size must be greater than zero.", "chunksize");
O(n²)
. Você pode percorrer a lista e obter umO(n)
tempo.source
é substituído por um empacotadoIEnumerable
cada vez. Assim, tendo elementos desource
passa através de camadas deSkip
sEm geral, a abordagem sugerida por CaseyB funciona bem; na verdade, se você está passando uma tarefa ,
List<T>
é difícil culpá-la, talvez eu a mude para:O que evitará enormes cadeias de chamadas. No entanto, essa abordagem tem uma falha geral. Ele materializa duas enumerações por bloco, para destacar o problema, tente executar:
Para superar isso, podemos tentar a abordagem de Cameron , que passa no teste acima em cores vivas, uma vez que apenas percorre a enumeração uma vez.
O problema é que ele tem uma falha diferente, materializa todos os itens de cada bloco, o problema dessa abordagem é que você fica com muita memória.
Para ilustrar isso, tente executar:
Por fim, qualquer implementação deve ser capaz de lidar com iteração fora de ordem de blocos, por exemplo:
Muitas soluções altamente ideais, como a minha primeira revisão desta resposta, falharam por lá. O mesmo problema pode ser visto na resposta otimizada do casperOne .
Para resolver todos esses problemas, você pode usar o seguinte:
Há também uma série de otimizações que você pode introduzir para iteração fora de ordem de blocos, que está fora do escopo aqui.
Quanto a qual método você deve escolher? Depende totalmente do problema que você está tentando resolver. Se você não está preocupado com a primeira falha, a resposta simples é incrivelmente atraente.
Observe que, como na maioria dos métodos, isso não é seguro para multiencadeamento, as coisas podem ficar estranhas se você desejar torná-lo seguro para threads, você precisará alterar
EnumeratorWrapper
.fonte
Você pode usar várias consultas que usam
Take
eSkip
, mas acredito que adicionariam muitas iterações à lista original.Em vez disso, acho que você deve criar um iterador próprio, assim:
Você pode chamar isso e ele está ativado para LINQ, para que você possa executar outras operações nas seqüências resultantes.
À luz da resposta de Sam , senti que havia uma maneira mais fácil de fazer isso sem:
Dito isto, aqui está outra passagem, que eu codifiquei em um método de extensão
IEnumerable<T>
chamadoChunk
:Nada de surpreendente lá em cima, apenas uma verificação básica de erros.
Passando para
ChunkInternal
:Basicamente, ele obtém
IEnumerator<T>
e itera manualmente através de cada item. Ele verifica se há algum item a ser enumerado no momento. Depois que cada pedaço é enumerado, se não houver nenhum item, ele será interrompido.Depois de detectar que há itens na sequência, ele delega a responsabilidade pela
IEnumerable<T>
implementação interna paraChunkSequence
:Como
MoveNext
já foi chamado noIEnumerator<T>
passadoChunkSequence
, ele gera o item retornadoCurrent
e depois incrementa a contagem, certificando-se de nunca retornar mais do quechunkSize
itens e passar para o próximo item na sequência após cada iteração (mas em curto-circuito se o número de itens produzidos excede o tamanho do pedaço).Se não houver itens restantes, o
InternalChunk
método fará outra passagem no loop externo, mas quandoMoveNext
for chamado pela segunda vez, ainda retornará falso, conforme a documentação (ênfase minha):Nesse ponto, o loop será interrompido e a sequência de sequências será encerrada.
Este é um teste simples:
Resultado:
Uma observação importante: isso não funcionará se você não drenar a sequência filho inteira ou interromper em qualquer ponto da sequência pai. Essa é uma ressalva importante, mas se o seu caso de uso for o de consumir todos os elementos da sequência de sequências, isso funcionará para você.
Além disso, ele fará coisas estranhas se você jogar com a ordem, assim como Sam fez em um ponto .
fonte
List<T>
, obviamente terá problemas de memória por causa do buffer. Em retrospecto, eu deveria ter notado isso na resposta, mas parecia que no momento o foco estava em muitas iterações. Dito isto, sua solução é realmente mais cabeluda. Não testei, mas agora me pergunto se há uma solução menos cabeluda.Ok, aqui está a minha opinião:
Exemplo de uso
Explicações
O código funciona aninhando dois
yield
iteradores baseados.O iterador externo deve acompanhar quantos elementos foram efetivamente consumidos pelo iterador interno (parte). Isso é feito encerrando
remaining
cominnerMoveNext()
. Os elementos não consumidos de um pedaço são descartados antes que o próximo pedaço seja produzido pelo iterador externo. Isso é necessário porque, caso contrário, você obtém resultados inconsistentes, quando os enumeráveis internos não são (completamente) consumidos (por exemploc3.Count()
, retornariam 6).fonte
completamente preguiçoso, sem contar ou copiar:
fonte
Eu acho que a sugestão a seguir seria a mais rápida. Estou sacrificando a preguiça da fonte Enumerable pela capacidade de usar Array.Copy e sabendo antecipadamente a duração de cada uma das minhas sublistas.
fonte
Podemos melhorar a solução do @ JaredPar para fazer uma verdadeira avaliação preguiçosa. Usamos um
GroupAdjacentBy
método que gera grupos de elementos consecutivos com a mesma chave:Como os grupos são gerados um por um, essa solução trabalha eficientemente com sequências longas ou infinitas.
fonte
Eu escrevi um método de extensão Clump há vários anos. Funciona muito bem e é a implementação mais rápida aqui. : P
fonte
System.Interactive fornece
Buffer()
para esta finalidade. Alguns testes rápidos mostram que o desempenho é semelhante à solução de Sam.fonte
Buffer()
retorna,IEnumerable<IList<T>>
então sim, você provavelmente terá um problema lá - ele não é transmitido como o seu.Aqui está uma rotina de divisão de listas que escrevi há alguns meses:
fonte
Acho que esse pequeno trecho faz o trabalho muito bem.
fonte
Que tal este?
Tanto quanto eu sei, GetRange () é linear em termos de número de itens obtidos. Portanto, isso deve ter um bom desempenho.
fonte
Essa é uma pergunta antiga, mas foi com isso que acabei; enumera o enumerável apenas uma vez, mas cria listas para cada uma das partições. Não sofre comportamento inesperado quando
ToArray()
é chamado, como algumas das implementações:fonte
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int chunkSize)
Descobrimos que a solução de David B funcionou melhor. Mas nós o adaptamos a uma solução mais geral:
fonte
Esta solução a seguir é a mais compacta que eu pude apresentar que é O (n).
fonte
Código antigo, mas é isso que eu tenho usado:
fonte
Se a lista for do tipo system.collections.generic, você poderá usar o método "CopyTo" disponível para copiar elementos da sua matriz para outras sub-matrizes. Você especifica o elemento inicial e o número de elementos a serem copiados.
Você também pode criar 3 clones da sua lista original e usar o "RemoveRange" em cada lista para reduzir a lista para o tamanho desejado.
Ou apenas crie um método auxiliar para fazer isso por você.
fonte
É uma solução antiga, mas eu tinha uma abordagem diferente. Eu uso
Skip
para mover para o deslocamento desejado eTake
extrair o número desejado de elementos:fonte
Para qualquer pessoa interessada em uma solução empacotada / mantida, a biblioteca MoreLINQ fornece o
Batch
método de extensão que corresponde ao comportamento solicitado:A
Batch
implementação é semelhante à resposta de Cameron MacFarland , com a adição de uma sobrecarga para transformar o bloco / lote antes de retornar e apresenta um bom desempenho.fonte
Usando particionamento modular:
fonte
Apenas colocando meus dois centavos. Se você quiser "agrupar" a lista (visualize da esquerda para a direita), faça o seguinte:
fonte
Outra maneira é usar o operador Rx Buffer
fonte
fonte
Peguei a resposta principal e fiz com que fosse um contêiner do COI para determinar onde dividir. ( Para quem realmente deseja dividir apenas três itens, ao ler esta postagem enquanto procura uma resposta? )
Este método permite dividir qualquer tipo de item, conforme necessário.
Portanto, para o OP, o código seria
fonte
Tão performático quanto a abordagem de Sam Saffron .
}
fonte
Pode trabalhar com geradores infinitos:
Código de demonstração: https://ideone.com/GKmL7M
Mas na verdade eu preferiria escrever o método correspondente sem linq.
fonte
Veja isso! Eu tenho uma lista de elementos com um contador de seqüência e data. Para cada vez que a sequência é reiniciada, desejo criar uma nova lista.
Ex. lista de mensagens.
Quero dividir a lista em listas separadas quando o contador reiniciar. Aqui está o código:
fonte
Para inserir meus dois centavos ...
Usando o tipo de lista para a fonte ser dividida, encontrei outra solução muito compacta:
fonte