Как сегментировать элементы, повторяемые в цикле foreach - PullRequest
9 голосов
/ 27 июля 2011

Мне нужно перебрать весь список пользователей, но нужно взять 20 одновременно.

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

Таким образом, первые 20 будут принадлежать группе 1, вторые 20 - группе 2 и т. Д.

Правильный ли синтаксис для этого? Я верю, что Take займет 20, а затем будет сделано. Спасибо!

Ответы [ 3 ]

15 голосов
/ 27 июля 2011

Вы можете сделать что-то вроде этого:

int i = 0;
foreach (var grouping in Class.Students.GroupBy(s => ++i / 20))
    Console.WriteLine("You belong to Group " + grouping.Key.ToString());
9 голосов
/ 28 июля 2011

Для аналогичной проблемы я однажды сделал метод расширения:

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

Обратите внимание, что последний чанк может быть меньше указанного размера чанка.

EDIT Первая версия использовала Skip()/Take(), но, как отметил Крис, GetRange значительно быстрее.

0 голосов
/ 28 июля 2011

Вы можете проецировать номер группы на каждый элемент в исходном списке, используя Select и анонимный тип, подобный этому:

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

Затем вы можете использовать его следующим образом:

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

Или если вы хотите выделить определенную группу.

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

Или если вы хотите на самом деле сгруппировать их для выполнения агрегатов

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

Теперь, если вы просто хотите объединитьэлементы в партиях, и вам не нужно иметь информацию о номере партии, вы можете создать этот простой метод расширения.

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

И вы можете использовать свой новый метод расширения следующим образом.

foreach (var batch in Class.Students.Batch(20))
{
  foreach (var student in batch)
  {
    Console.WriteLine("Student = " + student.Name);
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...