Programing

IEnumerable을 일괄 적으로 반복하는 방법

crosscheck 2020. 10. 31. 09:21
반응형

IEnumerable을 일괄 적으로 반복하는 방법


이 질문에 이미 답변이 있습니다.

400 만명의 아이디를 저장하는 "IEnumerable users"가있는 ac # 프로그램을 개발 중입니다. Ienummerable을 반복하고 다른 메서드에서 일부 작업을 수행하기 위해 매번 배치 1000 ID를 추출해야합니다.

Ienumerable 시작부터 한 번에 1000 개의 ID를 추출하는 방법 ... 다른 작업을 수행 한 다음 1000 개의 다음 배치를 가져 오는 등의 작업을 수행합니까?

이것이 가능한가?


개체의 Skip 및 Take 메서드를 사용해야하는 것 같습니다. 예:

users.Skip(1000).Take(1000)

이렇게하면 처음 1000 개를 건너 뛰고 다음 1000 개를 가져옵니다. 각 호출에서 건너 뛴 양을 늘려야합니다.

건너 뛰기 매개 변수와 함께 정수 변수를 사용할 수 있으며 건너 뛰는 양을 조정할 수 있습니다. 그런 다음 메서드에서 호출 할 수 있습니다.

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

당신은 사용할 수 있습니다 MoreLINQ의 배치 연산자 (NuGet에서 사용할 수를)

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

간단한 라이브러리 사용이 옵션이 아닌 경우 구현을 재사용 할 수 있습니다.

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

성능을 위해 BTW를 호출하지 않고 간단히 버킷을 반환 할 수 있습니다 Select(x => x). 선택은 배열에 최적화되어 있지만 선택기 대리자는 여전히 각 항목에서 호출됩니다. 따라서 귀하의 경우에는 사용하는 것이 좋습니다

yield return bucket;

이를 수행하는 가장 쉬운 방법은 아마도 GroupByLINQ 에서 메서드 를 사용하는 것입니다.

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

그러나보다 정교한 솔루션에 대해서는이 를 수행하기위한 자체 확장 메서드를 만드는 방법에 대한 블로그 게시물참조하십시오 . 후손을 위해 여기에 복제 :

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

이것을 사용해보십시오 :

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

위의 기능을 사용하려면 :

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

}

Take 및 Skip Enumerable 확장 방법을 사용하여이를 달성 할 수 있습니다. 사용량 확인 linq 101 에 대한 자세한 정보


다음과 같이 작동합니다.

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
}

그리고 이것을 다음과 같은 일반적인 방법으로 일반화 할 수 있습니다.

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

어때

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

당신이 사용할 수있는 Take operator linq

링크 : http://msdn.microsoft.com/fr-fr/library/vstudio/bb503062.aspx


열거자가 배치 중간에 차단 될 수있는 스트리밍 컨텍스트에서는 값이 아직 생성되지 않았기 때문에 (yield) 지정된 시간 후에 마지막 배치가 생성되도록 제한 시간 메서드를 사용하는 것이 유용합니다. 예를 들어 MongoDB에서 커서를 꼬리에 사용했습니다. 열거가 다른 스레드에서 수행되어야하므로 약간 복잡합니다.

    public static IEnumerable<List<T>> TimedBatch<T>(this IEnumerable<T> collection, double timeoutMilliseconds, long maxItems)
    {
        object _lock = new object();
        List<T> batch = new List<T>();
        AutoResetEvent yieldEventTriggered = new AutoResetEvent(false);
        AutoResetEvent yieldEventFinished = new AutoResetEvent(false);
        bool yieldEventTriggering = false; 

        var task = Task.Run(delegate
        {
            foreach (T item in collection)
            {
                lock (_lock)
                {
                    batch.Add(item);

                    if (batch.Count == maxItems)
                    {
                        yieldEventTriggering = true;
                        yieldEventTriggered.Set();
                    }
                }

                if (yieldEventTriggering)
                {
                    yieldEventFinished.WaitOne(); //wait for the yield to finish, and batch to be cleaned 
                    yieldEventTriggering = false;
                }
            }
        });

        while (!task.IsCompleted)
        {
            //Wait for the event to be triggered, or the timeout to finish
            yieldEventTriggered.WaitOne(TimeSpan.FromMilliseconds(timeoutMilliseconds));
            lock (_lock)
            {
                if (batch.Count > 0) //yield return only if the batch accumulated something
                {
                    yield return batch;
                    batch.Clear();
                    yieldEventFinished.Set();
                }
            }
        }
        task.Wait();
    }

참고 URL : https://stackoverflow.com/questions/15414347/how-to-loop-through-ienumerable-in-batches

반응형