Quelqu'un peut-il suggérer un moyen de créer des lots d'une certaine taille dans linq?
Dans l'idéal, je veux pouvoir effectuer des opérations par blocs d'une certaine quantité configurable.
Vous n'avez pas besoin d'écrire de code. Utilisez la méthode MoreLINQ Batch, qui regroupe la séquence source dans des compartiments dimensionnés (MoreLINQ est disponible en tant que package NuGet que vous pouvez installer):
int size = 10;
var batches = sequence.Batch(size);
Qui est implémenté comme:
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)
et l'utilisation serait:
PRODUCTION:
la source
GroupBy
énumération commencée, n'a-t-il pas besoin d'énumérer complètement sa source? Cela perd une évaluation paresseuse de la source et donc, dans certains cas, tous les avantages du batching!Si vous commencez par
sequence
défini comme anIEnumerable<T>
, et que vous savez qu'il peut être énuméré plusieurs fois en toute sécurité (par exemple parce qu'il s'agit d'un tableau ou d'une liste), vous pouvez simplement utiliser ce modèle simple pour traiter les éléments par lots:la source
Tous les éléments ci-dessus fonctionnent terriblement avec de gros lots ou un espace mémoire faible. J'ai dû écrire le mien qui sera pipeline (ne remarquez aucune accumulation d'objets nulle part):
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, cela ne fonctionne pas:
la source
Il s'agit d'une implémentation à une fonction de Batch, totalement paresseuse, à faible surcharge, qui ne fait aucune accumulation. Basé sur (et corrige les problèmes dans) la solution de Nick Whaley avec l'aide d'EricRoller.
L'itération provient directement du IEnumerable sous-jacent, de sorte que les éléments doivent être énumérés dans un ordre strict et accessibles pas plus d'une fois. Si certains éléments ne sont pas consommés dans une boucle interne, ils sont rejetés (et essayer d'y accéder à nouveau via un itérateur enregistré sera lancé
InvalidOperationException: Enumeration already finished.
).Vous pouvez tester un échantillon complet sur .NET Fiddle .
la source
done
en appelant toujourse.Count()
aprèsyield return e
. Vous devrez réorganiser la boucle dans BatchInner pour ne pas appeler le comportement non définisource.Current
sii >= size
. Cela éliminera le besoin d'allouer un nouveauBatchInner
pour chaque lot.i
donc ce n'est pas nécessairement plus efficace que de définir une classe séparée, mais c'est un peu plus propre, je pense.Je me demande pourquoi personne n'a jamais publié de solution à l'ancienne. En voici une:
Cette simplicité est possible car la méthode Take:
Avertissement:
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é. Cela peut entraîner plusieurs 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. C'est toujours une solution lente puisque skip énumérera la collection à chaque fois qu'elle est appelée.
Cela peut également être résolu en utilisant la
GetRange
méthode, mais cela nécessite un calcul supplémentaire pour extraire un éventuel lot de repos:Voici une troisième façon de gérer cela, qui fonctionne avec 2 boucles. Cela garantit que la collection n'est énumérée qu'une seule fois!:
la source
Skip
etTake
à 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é. Cela peut entraîner plusieurs exécutions d'une requête de base de données, d'une requête Web ou d'une lecture de fichier. Dans votre exemple, vous avez unList
qui n'est pas différé, donc c'est moins un problème.Même approche que MoreLINQ, mais en utilisant List au lieu de Array. Je n'ai pas fait de benchmarking, mais la lisibilité est plus importante pour certaines personnes:
la source
size
paramètre à votrenew List
pour optimiser sa taille.batch.Clear();
batch = new List<T>();
Voici une tentative d'amélioration des implémentations paresseuses de Nick Whaley ( lien ) et d'infogulch ( lien )
Batch
. Celui-ci est strict. Soit vous énumérez les lots dans le bon ordre, soit vous obtenez une exception.Et voici une
Batch
implémentation paresseuse pour les sources de typeIList<T>
. Celui-ci n'impose aucune restriction sur le dénombrement. Les lots peuvent être énumérés partiellement, dans n'importe quel ordre et plusieurs fois. La restriction de ne pas modifier la collection pendant l'énumération est cependant toujours en place. Ceci est réalisé en effectuant un appel facticeenumerator.MoveNext()
avant de donner un morceau ou un élément. L'inconvénient est que l'énumérateur n'est pas éliminé, car on ne sait pas quand l'énumération va se terminer.la source
Je rejoins ça très tard mais j'ai trouvé quelque chose de plus intéressant.
Nous pouvons donc l'utiliser ici
Skip
etTake
pour de meilleures performances.Ensuite, j'ai vérifié avec 100000 enregistrements. La boucle seule prend plus de temps en cas de
Batch
Code de l'application console.
Le temps pris est comme ça.
Première - 00: 00: 00.0708, 00: 00: 00.0660
Deuxième (Take and Skip One) - 00: 00: 00.0008, 00: 00: 00.0008
la source
GroupBy
énumère complètement avant de produire une seule ligne. Ce n'est pas une bonne façon de procéder au traitement par lots.foreach (var batch in Ids2.Batch(5000))
àvar gourpBatch = Ids2.Batch(5000)
et vérifiez les résultats chronométrés. ou ajouter tolist àvar SecBatch = Ids2.Batch2(StartIndex, BatchSize);
je serais intéressé si vos résultats pour le changement de timing.Donc, avec un chapeau fonctionnel, cela semble trivial ... mais en C #, il y a des inconvénients importants.
vous verriez probablement cela comme un déroulement de IEnumerable (google et vous vous retrouverez probablement dans certains documents Haskell, mais il peut y avoir des trucs F # utilisant déplier, si vous connaissez F #, louchez sur les documents Haskell et cela fera sens).
Déplier est lié au repli ("agrégat") sauf qu'au lieu d'itérer via l'entrée IEnumerable, il itère à travers les structures de données de sortie (c'est une relation similaire entre IEnumerable et IObservable, en fait, je pense que IObservable implémente un "déplier" appelé générer. ..)
de toute façon vous avez d'abord besoin d'une méthode de dépliage, je pense que cela fonctionne (malheureusement, cela finira par faire exploser la pile pour les grandes "listes" ... vous pouvez l'écrire en toute sécurité en F # en utilisant yield! plutôt que concat);
c'est un peu obtus parce que C # n'implémente pas certaines des choses que les langages fonctionnels tiennent pour acquises ... mais il prend essentiellement une graine et génère ensuite une réponse "Peut-être" de l'élément suivant dans IEnumerable et de la graine suivante (Peut-être n'existe pas en C #, donc nous avons utilisé IEnumerable pour le simuler), et concatène le reste de la réponse (je ne peux pas garantir la complexité "O (n?)" de ceci).
Une fois que vous avez fait cela, alors;
tout semble assez propre ... vous prenez les éléments "n" comme élément "suivant" dans IEnumerable, et la "queue" est le reste de la liste non traitée.
s'il n'y a rien dans la tête ... vous avez terminé ... vous retournez "Nothing" (mais truqué comme un IEnumerable vide>) ... sinon vous retournez l'élément head et la queue à traiter.
vous pouvez probablement le faire en utilisant IObservable, il y a probablement déjà une méthode de type "Batch", et vous pouvez probablement l'utiliser.
Si le risque de débordement de pile vous inquiète (c'est probablement le cas), alors vous devriez l'implémenter en F # (et il y a probablement déjà une bibliothèque F # (FSharpX?) Avec ça).
(Je n'ai fait que quelques tests rudimentaires à ce sujet, il peut donc y avoir des bugs étranges).
la source
J'ai écrit une implémentation IEnumerable personnalisée qui fonctionne sans linq et garantit une seule énumération sur les données. Il accomplit également tout cela sans nécessiter de listes de sauvegarde ou de tableaux qui provoquent des explosions de mémoire sur de grands ensembles de données.
Voici quelques tests de base:
La méthode d'extension pour partitionner les données.
C'est la classe d'implémentation
la source
Je sais que tout le monde a utilisé des systèmes complexes pour faire ce travail, et je ne comprends vraiment pas pourquoi. Take and skip autorisera toutes ces opérations en utilisant la
Func<TSource,Int32,TResult>
fonction de sélection commune avec transformation. Comme:la source
source
sera répété très souvent.Enumerable.Range(0, 1).SelectMany(_ => Enumerable.Range(0, new Random().Next()))
.Juste une autre implémentation d'une ligne. Cela fonctionne même avec une liste vide, dans ce cas, vous obtenez une collection de lots de taille nulle.
la source
Une autre façon consiste à utiliser l' opérateur Rx Buffer
la source
GetAwaiter().GetResult()
. Il s'agit d'une odeur de code pour le code synchrone appelant avec force du code asynchrone.la source