【问题标题】:How do I segment the elements iterated over in a foreach loop如何分割在 foreach 循环中迭代的元素
【发布时间】:2011-10-14 12:38:34
【问题描述】:

我需要遍历整个用户列表,但需要一次抓取 20 个。

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

这样前 20 个将属于第 1 组,后 20 个将属于第 2 组,依此类推。

采取正确的语法吗?我相信 Take 需要 20 次才能完成。谢谢!

【问题讨论】:

    标签: c# linq foreach


    【解决方案1】:

    你可以这样做:

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

    【讨论】:

    • 感谢 MikeP!这很有帮助。如果我需要为小组中的每个学生分配一个特定的值,我该怎么做?我觉得我必须在某个时候为每个嵌套做一个。
    • 分组对象对于分组到其中的元素也是可枚举的。所以只需要一个 foreach(var student in group) 就可以了。
    • 这段代码不会因为预增量而出现非一错误吗?本质上,您是从 i = 1 而不是 0 开始进行迭代。例如,如果您的组大小为 2,则可枚举的第一个元素将单独在第一个分组中,因为 (0 + 1) / 2 == 0 但是(1 + 1) / 2 == 1.
    【解决方案2】:

    您可以使用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);
      }
    }
    

    【讨论】:

    • 你和许多其他发帖者一样犯了同样的错误,他们已经删除了他们的回复。你不会连续拿走 20 件物品;前 20 名学生中的每一个都将在不同的组中!
    • 此外,您实际上也没有对任何内容进行分组。如果你想迭代一个组中的学生,你的方法会让你变得高高在上。
    • @MikeP:是的,不错。固定的。我还将grouped 重命名为assigned,以更好地反映实际发生的情况。如果 OP 真的想从特定组中迭代学生,您可以执行 assigned.Where(x =&gt; x.GroupNumber == value)assigned.GroupBy(x =&gt; x.GroupNumber),尽管根据我在问题中看到的内容,这看起来没有必要。我想 LINQ 的一个有趣的方面是有很多方法可以做同样的事情。无论如何,是的,我完全被 % 而不是 / 所窒息。
    【解决方案3】:

    类似的问题我曾经做过一个扩展方法:

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

    注意最后一个块可能小于指定的块大小。

    编辑 第一个版本使用 Skip()/Take(),但正如 Chris 指出的,GetRange 要快得多。

    【讨论】:

    • GetRange 比 Skip/Take 快 4000 倍以上
    • 嘿,谢谢@ChrisGessler。发现自己忽略了一种做得更好的“好旧”方法的存在是很尴尬的(虽然我总是对自己说 linq 并不是所有事情的答案)。在包含大量和非原始对象的基准测试中,我发现 GetRange 比 Skip()/Take() 提高了 10 多倍。好电话。
    • @GertArnold 这是一个非常古老的答案,但您能否提供一个异步版本来回答我的问题:stackoverflow.com/questions/66321310/…
    猜你喜欢
    • 2011-01-02
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多