Quelqu'un peut-il suggérer un moyen de créer des lots d'une certaine taille en linq ?
Idéalement, je voudrais pouvoir effectuer des opérations par tranches d'une certaine quantité configurable.
Quelqu'un peut-il suggérer un moyen de créer des lots d'une certaine taille en linq ?
Idéalement, je voudrais pouvoir effectuer des opérations par tranches d'une certaine quantité configurable.
Tous les systèmes ci-dessus fonctionnent très mal avec des lots importants ou un espace mémoire réduit. J'ai dû écrire mon propre programme qui fonctionne en pipeline (remarquez qu'il n'y a pas d'accumulation d'éléments nulle part) :
public static class BatchLinq {
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size) {
if (size <= 0)
throw new ArgumentOutOfRangeException("size", "Must be greater than zero.");
using (IEnumerator<T> enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
yield return TakeIEnumerator(enumerator, size);
}
private static IEnumerable<T> TakeIEnumerator<T>(IEnumerator<T> source, int size) {
int i = 0;
do
yield return source.Current;
while (++i < size && source.MoveNext());
}
}
Edit : Le problème connu avec cette approche est que chaque lot doit être énuméré et entièrement énuméré avant de passer au lot suivant. Par exemple, ceci ne fonctionne pas :
//Select first item of every 100 items
Batch(list, 100).Select(b => b.First())
Je me demande pourquoi personne n'a jamais posté de solution de type "for-loop" à l'ancienne. En voici une :
List<int> source = Enumerable.Range(1,23).ToList();
int batchsize = 10;
for (int i = 0; i < source.Count; i+= batchsize)
{
var batch = source.Skip(i).Take(batchsize);
}
Cette simplicité est possible grâce à la méthode Take :
... énumère
source
et donne des éléments jusqu'à ce quecount
les éléments ont été cédés ousource
ne contient plus d'éléments. Sicount
dépasse le nombre d'éléments danssource
tous les éléments desource
sont retournés
Avis de non-responsabilité :
L'utilisation de Skip et Take à l'intérieur de la boucle signifie que l'énumérable sera énuméré plusieurs fois. Ceci est dangereux si l'énumérable est différé. Il peut en résulter de multiples exécutions d'une requête de base de données, d'une requête web ou d'une lecture de fichier. Cet exemple est explicitement pour l'utilisation d'une liste qui n'est pas différée, donc c'est moins un problème. Il s'agit toujours d'une solution lente puisque skip énumérera la collection à chaque appel.
Ce problème peut également être résolu en utilisant le GetRange
mais elle nécessite un calcul supplémentaire pour extraire un éventuel lot de repos :
for (int i = 0; i < source.Count; i += batchsize)
{
int remaining = source.Count - i;
var batch = remaining > batchsize ? source.GetRange(i, batchsize) : source.GetRange(i, remaining);
}
Voici une troisième façon de procéder, qui fonctionne avec 2 boucles. Cela garantit que la collection n'est énumérée qu'une seule fois !
int batchsize = 10;
List<int> batch = new List<int>(batchsize);
for (int i = 0; i < source.Count; i += batchsize)
{
// calculated the remaining items to avoid an OutOfRangeException
batchsize = source.Count - i > batchsize ? batchsize : source.Count - i;
for (int j = i; j < i + batchsize; j++)
{
batch.Add(source[j]);
}
batch.Clear();
}
Voici une tentative d'amélioration de celle de Nick Whaley ( lien ) et celui d'infogulch ( lien ) paresseux Batch
mises en œuvre. Celle-ci est stricte. Soit vous énumérez les lots dans l'ordre correct, soit vous obtenez une exception.
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IEnumerable<TSource> source, int size)
{
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
using (var enumerator = source.GetEnumerator())
{
int i = 0;
while (enumerator.MoveNext())
{
if (i % size != 0) throw new InvalidOperationException(
"The enumeration is out of order.");
i++;
yield return GetBatch();
}
IEnumerable<TSource> GetBatch()
{
while (true)
{
yield return enumerator.Current;
if (i % size == 0 || !enumerator.MoveNext()) break;
i++;
}
}
}
}
Et voici un paresseux Batch
pour les sources de type IList<T>
. Celle-ci n'impose aucune restriction sur l'énumération. Les lots peuvent être énumérés partiellement, dans n'importe quel ordre, et plus d'une fois. La restriction de ne pas modifier la collection pendant l'énumération est cependant toujours en place. Ceci est réalisé en faisant un appel factice à enumerator.MoveNext()
avant de donner un morceau ou un élément. L'inconvénient est que l'énumérateur est laissé non disposé, puisqu'on ne sait pas quand l'énumération va se terminer.
public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
this IList<TSource> source, int size)
{
if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
var enumerator = source.GetEnumerator();
for (int i = 0; i < source.Count; i += size)
{
enumerator.MoveNext();
yield return GetChunk(i, Math.Min(i + size, source.Count));
}
IEnumerable<TSource> GetChunk(int from, int toExclusive)
{
for (int j = from; j < toExclusive; j++)
{
enumerator.MoveNext();
yield return source[j];
}
}
}
Même approche que MoreLINQ, mais en utilisant une liste au lieu d'un tableau. Je n'ai pas fait de benchmarking, mais la lisibilité est plus importante pour certaines personnes :
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int size)
{
List<T> batch = new List<T>();
foreach (var item in source)
{
batch.Add(item);
if (batch.Count >= size)
{
yield return batch;
batch.Clear();
}
}
if (batch.Count > 0)
{
yield return batch;
}
}
Voici la version la plus propre de Batch
que je peux trouver :
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int count)
{
if (source == null) throw new System.ArgumentNullException("source");
if (count <= 0) throw new System.ArgumentOutOfRangeException("count");
using (var enumerator = source.GetEnumerator())
{
IEnumerable<T> BatchInner()
{
int counter = 0;
do
yield return enumerator.Current;
while (++counter < count && enumerator.MoveNext());
}
while (enumerator.MoveNext())
yield return BatchInner().ToArray();
}
}
En utilisant ce code :
Console.WriteLine(String.Join(Environment.NewLine,
Enumerable.Range(0, 20).Batch(8).Select(xs => String.Join(",", xs))));
J'ai compris :
0,1,2,3,4,5,6,7
8,9,10,11,12,13,14,15
16,17,18,19
Il est important de noter que sur les réponses de "" & "" que ce code échoue :
var e = Enumerable.Range(0, 20).Batch(8).ToArray();
Console.WriteLine(String.Join(Environment.NewLine, e.Select(xs => String.Join(",", xs))));
Console.WriteLine();
Console.WriteLine(String.Join(Environment.NewLine, e.Select(xs => String.Join(",", xs))));
Sur leurs réponses, il donne :
19
19
19
19
19
19
En raison de l'énumération interne qui n'est pas calculée comme un tableau.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.