Estou tentando dividir uma lista em uma série de listas menores.
Meu problema: minha função de dividir listas não as divide em listas do tamanho correto. Deve dividi-los em listas de tamanho 30, mas em vez disso os divide em listas de tamanho 114?
Como posso fazer minha função dividir uma lista em um número X de listas de tamanho 30 ou menos ?
public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30)
{
List<List<float[]>> list = new List<List<float[]>>();
for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) {
List <float[]> subLocat = new List <float[]>(locations);
if (subLocat.Count >= ((i*nSize)+nSize))
subLocat.RemoveRange(i*nSize, nSize);
else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize));
Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString());
list.Add (subLocat);
}
return list;
}
Se eu usar a função em uma lista de tamanho 144, a saída será:
Índice: 4, Tamanho: 120
Índice: 3, Tamanho: 114
Índice: 2, Tamanho: 114
Índice: 1, Tamanho: 114
Índice: 0, Tamanho: 114
Respostas:
Versão genérica:
fonte
GetRange(3, 3)
Eu sugeriria usar esse método de extensão para dividir a lista de fontes nas sub-listas pelo tamanho de bloco especificado:
Por exemplo, se você dividir a lista de 18 itens por 5 itens por bloco, ela fornecerá a lista de 4 sub-listas com os seguintes itens: 5-5-5-3.
fonte
ToList()
e deixe a avaliação preguiçosa fazer a mágica.e quanto a:
fonte
ToList
mas eu não me incomodaria em tentar otimizá-lo - é tão trivial e improvável é um gargalo. O principal ganho dessa implementação é sua trivialidade, que é fácil de entender. Se você quiser, pode usar a resposta aceita, ela não cria essas listas, mas é um pouco mais complexa..Skip(n)
itera osn
elementos toda vez que é chamado, enquanto isso pode ser bom, é importante considerar o código crítico de desempenho. stackoverflow.com/questions/20002975/….Skip()
s na base de código da minha empresa e, embora não sejam "ideais", eles funcionam muito bem. Coisas como operações de banco de dados levam muito mais tempo. Mas acho importante notar que.Skip()
"toca" cada elemento <n em seu caminho, em vez de pular diretamente para o enésimo elemento (como você poderia esperar). Se o seu iterador tiver efeitos colaterais ao tocar em um elemento,.Skip()
pode ser a causa de erros difíceis de encontrar.A solução Serj-Tm é boa, também esta é a versão genérica como método de extensão para listas (coloque-a em uma classe estática):
fonte
Acho a resposta aceita (Serj-Tm) mais robusta, mas gostaria de sugerir uma versão genérica.
fonte
A biblioteca MoreLinq tem o método chamado
Batch
O resultado é
ids
são divididos em 5 pedaços com 2 elementos.fonte
Eu tenho um método genérico que poderia levar qualquer tipo, incluindo float, e foi testado por unidade, espero que ajude:
fonte
values.Count()
causará uma enumeração completa e depoisvalues.ToList()
outra. Mais segurovalues = values.ToList()
, já está materializado.Embora muitas das respostas acima façam o trabalho, todas elas falham terrivelmente em uma sequência sem fim (ou uma sequência realmente longa). A seguir, uma implementação totalmente on-line que garante a melhor complexidade possível de tempo e memória. Apenas iteramos a fonte enumerável exatamente uma vez e usamos o retorno de rendimento para uma avaliação lenta. O consumidor pode jogar fora a lista em cada iteração, tornando o espaço de memória igual ao da lista com
batchSize
número de elementos.Edição: Agora, percebendo que o OP pergunta sobre dividir um
List<T>
em menorList<T>
, então meus comentários sobre enumeráveis infinitos não são aplicáveis ao OP, mas podem ajudar outras pessoas que acabam aqui. Esses comentários foram uma resposta a outras soluções postadas que usamIEnumerable<T>
como entrada para sua função, mas enumeram a fonte enumerável várias vezes.fonte
IEnumerable<IEnumerable<T>>
versão é melhor, pois não envolve muitaList
construção.IEnumerable<IEnumerable<T>>
é que a implementação provavelmente dependerá do consumidor enumerar completamente cada enumerável interno gerado. Tenho certeza de que uma solução pode ser formulada de maneira a evitar esse problema, mas acho que o código resultante pode ficar complexo rapidamente. Além disso, como é preguiçoso, estamos gerando apenas uma lista por vez e a alocação de memória acontece exatamente uma vez por lista, pois sabemos o tamanho antecipadamente.Adição após comentário muito útil de mhand no final
Resposta original
Embora a maioria das soluções possa funcionar, acho que não são muito eficientes. Suponha que você queira apenas os primeiros itens dos primeiros pedaços. Então você não gostaria de iterar sobre todos os (zilhões) itens em sua sequência.
O seguinte irá, no máximo, enumerar duas vezes: uma para o Take e outra para o Skip. Não enumerará mais elementos do que você usará:
Quantas vezes isso enumerará a sequência?
Suponha que você divida sua fonte em pedaços de
chunkSize
. Você enumera apenas os primeiros N pedaços. De cada pedaço enumerado, você enumerará apenas os primeiros M elementos.o Any receberá o Enumerador, faça 1 MoveNext () e retornará o valor retornado após Disposing the Enumerator. Isso será feito N vezes
De acordo com a fonte de referência, isso fará algo como:
Isso não faz muito até você começar a enumerar sobre o Chunk buscado. Se você buscar vários Chunks, mas decidir não enumerar o primeiro Chunk, o foreach não será executado, pois seu depurador mostrará a você.
Se você decidir pegar os primeiros M elementos do primeiro pedaço, o retorno do rendimento será executado exatamente M vezes. Isso significa:
Depois que o primeiro pedaço tiver sido retornado, pulamos esse primeiro pedaço:
Mais uma vez: vamos dar uma olhada na fonte de referência para encontrar o
skipiterator
Como você vê, as
SkipIterator
chamadas são feitasMoveNext()
uma vez para cada elemento no Chunk. Não chamaCurrent
.Portanto, por Chunk, vemos o seguinte:
Leva():
Se o conteúdo estiver enumerado: GetEnumerator (), um MoveNext e um Current por item enumerado, Dispose enumerator;
Skip (): para cada pedaço enumerado (NÃO o conteúdo do pedaço): GetEnumerator (), MoveNext () chunkSize times, no Current! Dispose enumerator
Se você observar o que acontece com o enumerador, verá muitas chamadas para MoveNext () e apenas chamadas para
Current
para os itens do TSource que você realmente decide acessar.Se você usar N Chunks de tamanho chunkSize, chamará MoveNext ()
Se você decidir enumerar apenas os primeiros M elementos de cada pedaço buscado, precisará chamar MoveNext M vezes por pedaço enumerado.
O total
Portanto, se você decidir enumerar todos os elementos de todos os pedaços:
Se o MoveNext é muito trabalhoso ou não, depende do tipo de sequência de origem. Para listas e matrizes, é um simples incremento de índice, talvez com uma verificação fora do intervalo.
Mas se o seu IEnumerable for o resultado de uma consulta ao banco de dados, verifique se os dados estão realmente materializados no seu computador, caso contrário, os dados serão buscados várias vezes. O DbContext e o Dapper transferirão adequadamente os dados para o processo local antes que possam ser acessados. Se você enumerar a mesma sequência várias vezes, ela não será buscada várias vezes. Dapper retorna um objeto que é uma Lista, o DbContext lembra que os dados já foram buscados.
Depende do seu Repositório se é sensato chamar AsEnumerable () ou ToLists () antes de começar a dividir os itens em Chunks.
fonte
2*chunkSize
tempos de origem ? Isso é mortal, dependendo da fonte do enumerável (talvez com suporte a DB ou outra fonte não memorizada). Imagine isso enumeráveis como entradaEnumerable.Range(0, 10000).Select(i => DateTime.UtcNow)
- você vai ter horários diferentes a cada vez que você enumerar a enumeráveis vez que não é memoizedEnumerable.Range(0, 10).Select(i => DateTime.UtcNow)
. Invocando,Any
você recalculará a hora atual de cada vez. Não é tão ruim assimDateTime.UtcNow
, mas considere um enumerável apoiado por uma conexão com o banco de dados / cursor sql ou similar. Já vi casos em que milhares de chamadas DB foram emitidos porque o desenvolvedor não entendia as potenciais repercussões de 'vários enumerações de um enumeráveis' - ReSharper fornece uma dica para isso tambémfonte
fonte
Que tal este? A ideia era usar apenas um loop. E, quem sabe, talvez você esteja usando apenas implementações do IList no seu código e não queira transmitir para a Lista.
fonte
Mais um
fonte
fonte
fonte
Eu havia encontrado essa mesma necessidade e usei uma combinação dos métodos Skip () e Take () do Linq . Eu multiplico o número que recebo pelo número de iterações até agora, e isso me dá o número de itens a serem ignorados, então eu pego o próximo grupo.
fonte
Com base em Dimitry Pavlov respondere eu removeria
.ToList()
. E também evite a classe anônima. Em vez disso, gosto de usar uma estrutura que não requer uma alocação de memória heap. (AValueTuple
também faria o trabalho.)Isso pode ser usado como o seguinte, que apenas repete a coleção uma vez e também não aloca nenhuma memória significativa.
Se uma lista concreta for realmente necessária, eu faria assim:
fonte