开发者

How do I segment the elements iterated over in a foreach loop

开发者 https://www.devze.com 2023-03-23 21:17 出处:网络
I need to loop through an entire list of users, but need to grab 20 at a time. foreach (var student in Class.Students.Take(20))

I need to loop through an entire list of users, but need to grab 20 at a time.

foreach (var student in Class.Students.Take(20))
{
   Console.WriteLine("You belong to Group " + groupNumber);
   groupNumber++;
}

This way the first 20 will belong to Group 1, the second 20 to Group 2, and so on.

Is Take the correct syntax for th开发者_JS百科is? I believe Take will take 20 then be done. Thanks!


You can do something like this:

int i = 0;
foreach (var grouping in Class.Students.GroupBy(s => ++i / 20))
    Console.WriteLine("You belong to Group " + grouping.Key.ToString());


For a similar problem I once made an extension method:

public static IEnumerable<IEnumerable<T>> ToChunks<T>(this IEnumerable<T> enumerable, int chunkSize)
{
    int itemsReturned = 0;
    var list = enumerable.ToList(); // Prevent multiple execution of IEnumerable.
    int count = list.Count;
    while (itemsReturned < count)
    {
        int currentChunkSize = Math.Min(chunkSize, count - itemsReturned);
        yield return list.GetRange(itemsReturned, currentChunkSize);
        itemsReturned += currentChunkSize;
    }
}

Note that the last chunk may be smaller than the specified chunk size.

EDIT The first version used Skip()/Take(), but as Chris pointed out, GetRange is considerably faster.


You could project the group number onto each item in the original list using Select and an anonymous type like this:

var assigned = Class.Students.Select(
  (item, index) => new 
  { 
    Student = item, 
    GroupNumber = index / 20 + 1 
  });

Then you can use it like this:

foreach (var item in assigned)
{
  Console.WriteLine("Student = " + item.Student.Name);
  Console.WriteLine("You belong to Group " + item.GroupNumber.ToString());
}

Or if you wanted to pick off a particular group.

foreach (var student in assigned.Where(x => x.GroupNumber == 5))
{
  Console.WriteLine("Name = " + student.Name);
}

Or if you wanted to actually group them to perform aggregates

foreach (var group in assigned.GroupBy(x => x.GroupNumber))
{
  Console.WriteLine("Average age = " + group.Select(x => x.Student.Age).Average().ToString());
}

Now if you just want to lump the items in batches and you do not care about having the batch number information you can create this simple extension method.

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> target, int size)
{
  var assigned = target.Select(
      (item, index) => new
      {
          Value = item,
          BatchNumber = index / size
      });
  foreach (var group in assigned.GroupBy(x => x.BatchNumber))
  {
      yield return group.Select(x => x.Value);
  }
}

And you could use your new extension method like this.

foreach (var batch in Class.Students.Batch(20))
{
  foreach (var student in batch)
  {
    Console.WriteLine("Student = " + student.Name);
  }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消