wake-up-neo.net

Durchlaufen Sie IEnumerable in Batches

Ich entwickle ein c # -Programm, das einen "IEnumerable-Benutzer" hat, der die IDs von 4 Millionen Benutzern speichert. Ich muss die Ienummerable durchlaufen und jedes Mal einen Stapel mit 1000 IDs extrahieren, um einige Operationen in einer anderen Methode auszuführen.

Wie extrahiere ich 1000 IDs gleichzeitig vom Start von Ienumerable ... etwas anderes, dann die nächste Charge von 1000 abrufen und so weiter?

Ist das möglich?

63
user1526912

Klingt, als müssten Sie die Skip- und Take-Methoden Ihres Objekts verwenden. Beispiel:

users.Skip(1000).Take(1000)

dies würde die ersten 1000 überspringen und die nächsten 1000 übernehmen. Sie müssen nur den Betrag erhöhen, der bei jedem Anruf übersprungen wird

Sie können eine ganzzahlige Variable mit dem Parameter für Skip verwenden und Sie können einstellen, wie viel übersprungen wird. Sie können es dann in einer Methode aufrufen.

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

Sie können den Batch-Operator von MoreLINQ verwenden (verfügbar bei NuGet):

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

Wenn die einfache Verwendung der Bibliothek nicht möglich ist, können Sie die Implementierung wiederverwenden:

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)            
        yield return bucket.Take(count);            
}

Übrigens können Sie Bucket für die Leistung einfach zurückgeben, ohne Select(x => x) aufzurufen. Select ist für Arrays optimiert, der Delegator für Selektoren wird jedoch weiterhin für jedes Element aufgerufen. In Ihrem Fall ist es also besser zu verwenden

yield return bucket;
114

Der einfachste Weg dazu ist wahrscheinlich die Verwendung der GroupBy -Methode in LINQ:

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

Eine anspruchsvollere Lösung finden Sie in diesem Blogbeitrag , wie Sie dazu eine eigene Erweiterungsmethode erstellen können. Hier für die Nachwelt dupliziert:

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;
}
26
p.s.w.g

versuchen Sie es mit diesem: 

  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;
    }

und obige Funktion verwenden:

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

}
9
Zaki

Das erreichen Sie mit der Erweiterungsmethode Take and Skip Enumerable. Weitere Informationen zur Nutzungsüberprüfung linq 101

So etwas würde funktionieren:

List<MyClass> batch = new List<MyClass>();
foreach (MyClass item in items)
{
    batch.Add(item);

    if (batch.Count == 1000)
    {
        // Perform operation on batch
        batch.Clear();
    }
}

// Process last batch
if (batch.Any())
{
    // Perform operation on batch
}

Und Sie könnten dies zu einer generischen Methode wie dieser verallgemeinern:

static void PerformBatchedOperation<T>(IEnumerable<T> items, 
                                       Action<IEnumerable<T>> operation, 
                                       int batchSize)
{
    List<T> batch = new List<T>();
    foreach (T item in items)
    {
        batch.Add(item);

        if (batch.Count == batchSize)
        {
            operation(batch);
            batch.Clear();
        }
    }

    // Process last batch
    if (batch.Any())
    {
        operation(batch);
    }
}
4
JLRishe

Wie wäre es mit

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);
}
0
user3852812

Sie können Take operator linq verwenden

Link: http://msdn.Microsoft.com/fr-fr/library/vstudio/bb503062.aspx

0
Aghilas Yakoub