Alguém pode sugerir uma maneira de criar lotes de um determinado tamanho no linq?
Idealmente, quero ser capaz de realizar operações em blocos de alguma quantidade configurável.
Você não precisa escrever nenhum código. Use o método MoreLINQ Batch, que agrupa a sequência de origem em intervalos de tamanho (MoreLINQ está disponível como um pacote NuGet que você pode instalar):
int size = 10;
var batches = sequence.Batch(size);
Que é implementado como:
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source, int size)
{
TSource[] bucket = null;
var count = 0;
foreach (var item in source)
{
if (bucket == null)
bucket = new TSource[size];
bucket[count++] = item;
if (count != size)
continue;
yield return bucket;
bucket = null;
count = 0;
}
if (bucket != null && count > 0)
yield return bucket.Take(count).ToArray();
}
Batch(new int[] { 1, 2 }, 1000000)
e o uso seria:
RESULTADO:
fonte
GroupBy
iniciada a enumeração, não é necessário enumerar totalmente sua fonte? Isso perde a avaliação preguiçosa da fonte e, portanto, em alguns casos, todos os benefícios do envio em lote!Se você começar com
sequence
definido como umIEnumerable<T>
, e sabe que pode ser enumerado com segurança várias vezes (por exemplo, porque é uma matriz ou uma lista), você pode apenas usar este padrão simples para processar os elementos em lotes:fonte
Todos os itens acima têm um desempenho terrível com lotes grandes ou pouco espaço de memória. Tive que escrever o meu próprio que irá pipeline (não observe nenhum acúmulo de item em qualquer lugar):
Editar: O problema conhecido com essa abordagem é que cada lote deve ser enumerado e enumerado totalmente antes de passar para o próximo lote. Por exemplo, isso não funciona:
fonte
Esta é uma implementação de uma função do Batch totalmente preguiçosa, com baixa sobrecarga e que não faz nenhum acúmulo. Baseado em (e corrige problemas na) solução de Nick Whaley com a ajuda de EricRoller.
A iteração vem diretamente do IEnumerable subjacente, portanto, os elementos devem ser enumerados em ordem estrita e acessados no máximo uma vez. Se alguns elementos não forem consumidos em um loop interno, eles serão descartados (e tentar acessá-los novamente por meio de um iterador salvo será lançado
InvalidOperationException: Enumeration already finished.
).Você pode testar uma amostra completa em .NET Fiddle .
fonte
done
sempre pagandoe.Count()
depoisyield return e
. Você precisaria reorganizar o loop no BatchInner para não invocar o comportamento indefinidosource.Current
sei >= size
. Isso eliminará a necessidade de alocar um novoBatchInner
para cada lote.i
então isso não é necessariamente mais eficiente do que definir uma classe separada, mas acho que é um pouco mais limpo.Eu me pergunto por que ninguém jamais postou uma solução para loop da velha escola. Aqui está um:
Essa simplicidade é possível porque o método Take:
Aviso Legal:
Usar Skip e Take dentro do loop significa que o enumerável será enumerado várias vezes. Isso é perigoso se o enumerável for adiado. Isso pode resultar em várias execuções de uma consulta de banco de dados, ou uma solicitação da web, ou uma leitura de arquivo. Este exemplo é explicitamente para o uso de uma Lista que não é adiada, portanto, é um problema menor. Ainda é uma solução lenta, pois skip enumerará a coleção sempre que for chamada.
Isso também pode ser resolvido usando o
GetRange
método, mas requer um cálculo extra para extrair um possível lote restante:Aqui está uma terceira maneira de lidar com isso, que funciona com 2 loops. Isso garante que a coleção seja enumerada apenas 1 vez !:
fonte
Skip
eTake
dentro do loop significa que o enumerável será enumerado várias vezes. Isso é perigoso se o enumerável for adiado. Isso pode resultar em várias execuções de uma consulta de banco de dados, ou uma solicitação da web, ou uma leitura de arquivo. No seu exemplo, você tem umList
que não é adiado, portanto, é menos problemático.Mesma abordagem que MoreLINQ, mas usando List em vez de Array. Não fiz benchmarking, mas a legibilidade é mais importante para algumas pessoas:
fonte
size
parâmetronew List
para otimizar seu tamanho.batch.Clear();
porbatch = new List<T>();
Aqui está uma tentativa de melhoria das implementações preguiçosas de Nick Whaley ( link ) e infogulch ( link )
Batch
. Este é estrito. Você enumera os lotes na ordem correta ou obtém uma exceção.E aqui está uma
Batch
implementação preguiçosa para fontes do tipoIList<T>
. Este não impõe restrições à enumeração. Os lotes podem ser enumerados parcialmente, em qualquer ordem e mais de uma vez. A restrição de não modificar a coleção durante a enumeração ainda existe. Isso é conseguido fazendo uma chamada fictícia paraenumerator.MoveNext()
antes de produzir qualquer pedaço ou elemento. A desvantagem é que o enumerador não é eliminado, pois não se sabe quando a enumeração vai terminar.fonte
Estou entrando muito tarde, mas achei algo mais interessante.
Portanto, podemos usar aqui
Skip
eTake
para melhor desempenho.Em seguida, verifiquei com 100.000 registros. O loop só está levando mais tempo no caso de
Batch
Código do aplicativo de console.
O tempo gasto é assim.
Primeiro - 00: 00: 00.0708, 00: 00: 00.0660
Segundo (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008
fonte
GroupBy
enumera totalmente antes de produzir uma única linha. Essa não é uma boa maneira de fazer lotes.foreach (var batch in Ids2.Batch(5000))
paravar gourpBatch = Ids2.Batch(5000)
e verifique os resultados cronometrados. ou adicione tolist avar SecBatch = Ids2.Batch2(StartIndex, BatchSize);
Eu estaria interessado se seus resultados para a mudança de tempo.Portanto, com um chapéu funcional, isso parece trivial ... mas em C #, existem algumas desvantagens significativas.
você provavelmente verá isso como um desdobramento de IEnumerable (pesquise no google e provavelmente acabará em alguns documentos do Haskell, mas pode haver algumas coisas do F # usando o desdobramento, se você conhece o F #, procure nos documentos do Haskell e isso fará sentido).
O desdobramento está relacionado a dobrar ("agregado"), exceto que, em vez de iterar por meio da entrada IEnumerable, ele itera por meio das estruturas de dados de saída (é uma relação semelhante entre IEnumerable e IObservable; na verdade, acho que IObservable implementa um "desdobrar" chamado gerar. ..)
de qualquer forma, primeiro você precisa de um método de desdobramento, eu acho que isso funciona (infelizmente ele acabará explodindo com grandes "listas" ... você pode escrever isso com segurança em F # usando yield! em vez de concat);
isto é um pouco obtuso porque C # não implementa algumas das coisas que as linguagens funcionais consideram certas ... mas basicamente pega uma semente e então gera uma resposta "Maybe" do próximo elemento no IEnumerable e a próxima semente (Maybe não existe em C #, então usamos IEnumerable para falsificá-lo) e concatena o resto da resposta (não posso garantir a complexidade "O (n?)" disso).
Depois de fazer isso;
tudo parece bastante limpo ... você pega os elementos "n" como o elemento "próximo" no IEnumerable, e a "cauda" é o resto da lista não processada.
se não há nada no head ... você acabou ... você retorna "Nothing" (mas fingido como um IEnumerable vazio>) ... senão você retorna o elemento head e o tail para processar.
você provavelmente pode fazer isso usando IObservable, provavelmente já existe um método do tipo "Lote" e você provavelmente pode usá-lo.
Se o risco de estouro de pilha preocupa (provavelmente deveria), então você deve implementar em F # (e provavelmente já existe alguma biblioteca F # (FSharpX?) Com isso).
(Eu só fiz alguns testes rudimentares disso, então pode haver alguns bugs estranhos lá).
fonte
Eu escrevi uma implementação personalizada de IEnumerable que funciona sem o linq e garante uma única enumeração sobre os dados. Ele também realiza tudo isso sem exigir listas de apoio ou matrizes que causam explosões de memória em grandes conjuntos de dados.
Aqui estão alguns testes básicos:
O método de extensão para particionar os dados.
Esta é a aula de implementação
fonte
Eu sei que todo mundo usou sistemas complexos para fazer esse trabalho, e eu realmente não entendo por quê. Pegar e pular permitirá todas essas operações usando a seleção comum com a
Func<TSource,Int32,TResult>
função de transformação. Gostar:fonte
source
será iterado com muita freqüência.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Apenas mais uma implementação de uma linha. Funciona mesmo com uma lista vazia, neste caso você obtém uma coleção de lotes de tamanho zero.
fonte
Outra maneira é usar o operador Rx Buffer
fonte
GetAwaiter().GetResult()
. Este é um cheiro de código para código síncrono chamando código assíncrono à força.fonte