¿Alguien puede sugerir una forma de crear lotes de cierto tamaño en linq?
Idealmente, quiero poder realizar operaciones en trozos de una cantidad configurable.
No es necesario que escriba ningún código. Use el método MoreLINQ Batch, que agrupa la secuencia de origen en depósitos de tamaño (MoreLINQ está disponible como un paquete NuGet que puede instalar):
int size = 10;
var batches = sequence.Batch(size);
Que se implementa 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)
y el uso sería:
SALIDA:
fuente
GroupBy
comienza la enumeración, ¿no tiene que enumerar completamente su fuente? Esto pierde la evaluación perezosa de la fuente y, por lo tanto, en algunos casos, ¡todos los beneficios del procesamiento por lotes!Si comienza con
sequence
definido como anIEnumerable<T>
, y sabe que se puede enumerar de manera segura varias veces (por ejemplo, porque es una matriz o una lista), puede usar este patrón simple para procesar los elementos en lotes:fuente
Todo lo anterior funciona terriblemente con lotes grandes o con poco espacio de memoria. Tuve que escribir el mío propio que se canalizará (observe que no hay acumulación de artículos en ninguna parte):
Editar: el problema conocido con este enfoque es que cada lote debe enumerarse y enumerarse por completo antes de pasar al siguiente lote. Por ejemplo, esto no funciona:
fuente
Esta es una implementación de Batch completamente perezosa, de baja sobrecarga y de una función que no hace ninguna acumulación. Basado en (y soluciona problemas en) la solución de Nick Whaley con la ayuda de EricRoller.
La iteración proviene directamente del IEnumerable subyacente, por lo que los elementos deben enumerarse en orden estricto y no se debe acceder a ellos más de una vez. Si algunos elementos no se consumen en un bucle interno, se descartan (e intentar acceder a ellos nuevamente a través de un iterador guardado arrojará
InvalidOperationException: Enumeration already finished.
).Puede probar una muestra completa en .NET Fiddle .
fuente
done
simplemente llamando siempree.Count()
despuésyield return e
. Debería reorganizar el bucle en BatchInner para no invocar el comportamiento indefinidosource.Current
sii >= size
. Esto eliminará la necesidad de asignar uno nuevoBatchInner
para cada lote.i
por lo que esto no es necesariamente más eficiente que definir una clase separada, pero creo que es un poco más limpio.Me pregunto por qué nadie ha publicado nunca una solución de ciclo forzado de la vieja escuela. Acá hay uno:
Esta simplicidad es posible porque el método Take:
Descargo de responsabilidad:
El uso de Skip y Take dentro del bucle significa que el enumerable se enumerará varias veces. Esto es peligroso si se aplaza el enumerable. Puede resultar en varias ejecuciones de una consulta de base de datos, una solicitud web o la lectura de un archivo. Este ejemplo es explícitamente para el uso de una Lista que no se aplaza, por lo que es un problema menor. Sigue siendo una solución lenta, ya que skip enumerará la colección cada vez que se llame.
Esto también se puede resolver usando el
GetRange
método, pero requiere un cálculo adicional para extraer un posible lote de reposo:Aquí hay una tercera forma de manejar esto, que funciona con 2 bucles. ¡Esto asegura que la colección se enumere solo 1 vez !:
fuente
Skip
yTake
dentro del bucle significa que el enumerable se enumerará varias veces. Esto es peligroso si se aplaza el enumerable. Puede resultar en varias ejecuciones de una consulta de base de datos, una solicitud web o la lectura de un archivo. En su ejemplo, tiene unaList
que no se aplaza, por lo que es un problema menor.El mismo enfoque que MoreLINQ, pero usando List en lugar de Array. No he realizado evaluaciones comparativas, pero la legibilidad es más importante para algunas personas:
fuente
size
parámetro a sunew List
para optimizar su tamaño.batch.Clear();
batch = new List<T>();
Aquí hay un intento de mejora de las implementaciones perezosas de Nick Whaley ( enlace ) e infogulch ( enlace )
Batch
. Éste es estricto. O enumera los lotes en el orden correcto o obtiene una excepción.Y aquí hay una
Batch
implementación perezosa para fuentes de tipoIList<T>
. Éste no impone restricciones a la enumeración. Los lotes se pueden enumerar parcialmente, en cualquier orden y más de una vez. Sin embargo, la restricción de no modificar la colección durante la enumeración sigue vigente. Esto se logra haciendo una llamada ficticia aenumerator.MoveNext()
antes de ceder cualquier fragmento o elemento. La desventaja es que el enumerador se deja sin disposición, ya que se desconoce cuándo terminará la enumeración.fuente
Me uniré a esto muy tarde pero encontré algo más interesante.
Entonces podemos usar aquí
Skip
yTake
para un mejor rendimiento.Luego verifiqué con 100000 registros. El bucle solo está tomando más tiempo en caso de
Batch
Código de la aplicación de consola.
El tiempo necesario es así.
Primero - 00: 00: 00.0708, 00: 00: 00.0660
Segundo (tomar y omitir uno) - 00: 00: 00.0008, 00: 00: 00.0008
fuente
GroupBy
enumera completamente antes de producir una sola fila. Esta no es una buena forma de realizar lotes.foreach (var batch in Ids2.Batch(5000))
avar gourpBatch = Ids2.Batch(5000)
y comprobar los resultados cronometrados. o agregue tolist a Mevar SecBatch = Ids2.Batch2(StartIndex, BatchSize);
interesaría si sus resultados de sincronización cambian.Entonces, con un sombrero funcional, esto parece trivial ... pero en C #, hay algunas desventajas importantes.
probablemente verá esto como un despliegue de IEnumerable (busque en Google y probablemente terminará en algunos documentos de Haskell, pero puede haber algunas cosas de F # usando desplegar, si conoce F #, entrecerre los ojos en los documentos de Haskell y hará sentido).
Unfold está relacionado con fold ("aggregate") excepto que en lugar de iterar a través de la entrada IEnumerable, itera a través de las estructuras de datos de salida (es una relación similar entre IEnumerable e IObservable, de hecho creo que IObservable implementa un "despliegue" llamado generate. ..)
de todos modos, primero necesita un método de despliegue, creo que esto funciona (desafortunadamente, eventualmente volará la pila para "listas" grandes ... puede escribir esto de manera segura en F # usando yield! en lugar de concat);
esto es un poco obtuso porque C # no implementa algunas de las cosas que los lenguajes funcionales dan por sentado ... pero básicamente toma una semilla y luego genera una respuesta "Quizás" del siguiente elemento en IEnumerable y la siguiente semilla (Quizás no existe en C #, por lo que hemos usado IEnumerable para falsificarlo), y concatena el resto de la respuesta (no puedo responder por la complejidad "O (n?)" de esto).
Una vez que haya hecho eso, entonces;
todo parece bastante limpio ... se toman los elementos "n" como el elemento "siguiente" en IEnumerable, y la "cola" es el resto de la lista sin procesar.
si no hay nada en la cabeza ... se acabó ... devuelve "Nada" (pero falsificado como un IEnumerable> vacío) ... de lo contrario, devuelve el elemento de la cabeza y la cola para procesar.
probablemente pueda hacer esto usando IObservable, probablemente ya haya un método similar a "Batch", y probablemente pueda usarlo.
Si el riesgo de desbordamiento de la pila le preocupa (probablemente debería), entonces debería implementar en F # (y probablemente ya haya alguna biblioteca F # (FSharpX?) Con esto).
(Solo he hecho algunas pruebas rudimentarias de esto, por lo que puede haber errores extraños allí).
fuente
Escribí una implementación personalizada de IEnumerable que funciona sin linq y garantiza una sola enumeración sobre los datos. También logra todo esto sin requerir listas de respaldo o matrices que causan explosiones de memoria en grandes conjuntos de datos.
Aquí hay algunas pruebas básicas:
El método de extensión para particionar los datos.
Esta es la clase de implementación
fuente
Sé que todos usaron sistemas complejos para hacer este trabajo, y realmente no entiendo por qué. Tomar y omitir permitirá todas esas operaciones utilizando la
Func<TSource,Int32,TResult>
función de selección común con transformación. Me gusta:fuente
source
repetirá con mucha frecuencia.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Solo otra implementación de una línea. Funciona incluso con una lista vacía, en este caso obtiene una colección de lotes de tamaño cero.
fuente
Otra forma es usar el operador Rx Buffer
fuente
GetAwaiter().GetResult()
. Este es un olor a código para código síncrono que llama forzosamente a código asíncrono.fuente