99 votes

Comment boucler sur IEnumerable par lots ?

Je développe un programme C# qui possède un "IEnumerable users" qui stocke les identifiants de 4 millions d'utilisateurs. J'ai besoin de boucler à travers l'IEnumerable et d'extraire un lot de 1000 identifiants à chaque fois pour effectuer certaines opérations dans une autre méthode.

Comment extraire 1000 ID à la fois du début de l'IEnumerable, faire quelque chose d'autre, puis extraire le lot suivant de 1000 ID et ainsi de suite ?

Est-ce possible ?

167voto

Sergey Berezovskiy Points 102044

Vous pouvez utiliser L'opérateur Batch de MoreLINQ (disponible à partir de NuGet) :

foreach(IEnumerable<User> batch in users.Batch(1000))
   // use batch

Si la simple utilisation de la bibliothèque n'est pas envisageable, vous pouvez réutiliser l'implémentation :

public static IEnumerable<IEnumerable<T>> Batch<T>(
        this IEnumerable<T> source, int size)
{
    T[] bucket = null;
    var count = 0;

    foreach (var item in source)
    {
       if (bucket == null)
           bucket = new T[size];

       bucket[count++] = item;

       if (count != size)                
          continue;

       yield return bucket.Select(x => x);

       bucket = null;
       count = 0;
    }

    // Return the last bucket with all remaining elements
    if (bucket != null && count > 0)
    {
        Array.Resize(ref bucket, count);
        yield return bucket.Select(x => x);
    }
}

Pour des raisons de performances, vous pouvez simplement retourner le seau sans appeler Select(x => x) . Select est optimisé pour les tableaux, mais le délégué du sélecteur serait toujours invoqué sur chaque élément. Donc, dans votre cas, il est préférable d'utiliser

yield return bucket;

59voto

Bill Points 715

Il semble que vous deviez utiliser les méthodes Skip et Take de votre objet. Exemple :

users.Skip(1000).Take(1000)

cela sauterait les 1000 premiers et prendrait les 1000 suivants. Il suffit d'augmenter la quantité sautée à chaque appel.

Vous pourriez utiliser une variable entière avec le paramètre pour le saut et vous pourriez ajuster la quantité qui est sautée. Vous pouvez ensuite l'appeler dans une méthode.

public IEnumerable<user> GetBatch(int pageNumber)
{
    return users.Skip(pageNumber * 1000).Take(1000);
}

31voto

p.s.w.g Points 81433

La façon la plus simple de le faire est probablement d'utiliser la fonction GroupBy dans LINQ :

var batches = myEnumerable
    .Select((x, i) => new { x, i })
    .GroupBy(p => (p.i / 1000), (p, i) => p.x);

Mais pour une solution plus sophistiquée, voyez ceci article de blog sur la façon de créer votre propre méthode d'extension pour faire cela. Dupliqué ici pour la postérité :

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> collection, int batchSize)
{
    List<T> nextbatch = new List<T>(batchSize);
    foreach (T item in collection)
    {
        nextbatch.Add(item);
        if (nextbatch.Count == batchSize)
        {
            yield return nextbatch;
            nextbatch = new List<T>(); 
            // or nextbatch.Clear(); but see Servy's comment below
        }
    }

    if (nextbatch.Count > 0)
        yield return nextbatch;
}

21voto

user3852812 Points 11

Et si

int batchsize = 5;
List<string> colection = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};
for (int x = 0; x < Math.Ceiling((decimal)colection.Count / batchsize); x++)
{
    var t = colection.Skip(x * batchsize).Take(batchsize);
}

17voto

Zaki Points 5242

Essayez d'utiliser ceci :

  public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
        this IEnumerable<TSource> source,
        int batchSize)
    {
        var batch = new List<TSource>();
        foreach (var item in source)
        {
            batch.Add(item);
            if (batch.Count == batchSize)
            {
                 yield return batch;
                 batch = new List<TSource>();
            }
        }

        if (batch.Any()) yield return batch;
    }

et d'utiliser la fonction ci-dessus :

foreach (var list in Users.Batch(1000))
{

}

Prograide.com

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.

Powered by:

X