【问题标题】:Create a nested List of Objects from a single List从单个列表创建嵌套的对象列表
【发布时间】:2023-03-18 21:12:01
【问题描述】:

我有一个项目列表(不确定它们是偶数还是奇数)。我想做的是,在这对 5(实际上是一个列表)中提取记录,创建另一个列表并将这对 5 个列表插入到该新列表中。

谢谢

我可以通过这样做创建一组项目

MyList
  .Zip(Enumerable.Range(0, MyList.Count()),
       (s, r) => new { 
          Group = r / 5, 
          Item = s })
  .GroupBy(i => i.Group, 
           g => g.Item)
  .ToList();

但我想生成一个嵌套列表。

【问题讨论】:

  • 你想做什么?如果您想一次创建 5 个项目的批次,有更简单、更快捷的方法。事实上,MoreLinq 已经为此提供了一个函数
  • 你能举例说明结果应该是什么吗?
  • 您能提供一个示例吗?假设,你有一个List<int> == {1, 2, 3, 4, 5, 6, 7, 8},请问你想要的结果是什么?
  • 我有一个包含 100 个项目的列表。我想每 5 个项目选择一次,然后列出这 5 个项目,这样最后我就有一个 20 个项目的列表,每个项目本身都有 5 个项目。假设我有 List == {obj1, obj2, obj3, ...obj20} 我需要的结果是 list = { {{obj1, obj2, ...obj5}, {obj6, obj7, ...obj10}, {obj11, obj12, ...obj15}, {obj16, obj17...obj20} }
  • 也许只需在最终的.ToList() 之前插入.Select(grp => grp.ToList())grp 将是继承 IEnumerable<>IGrouping<,> 类型,因此 grp.ToList() 是有效的。

标签: c# collections


【解决方案1】:

不确定我是否正确理解您的目标,但您可以尝试使用字典:

MyList.Zip(Enumerable.Range(0, MyList.Count()),
  (s, r) => new { Group = r / 5, Item = s })
.GroupBy(i => i.Group, g => g.Item)
.ToDictionary(g => g.Key, g => g.ToList());

【讨论】:

    【解决方案2】:

    您似乎想要批处理 元素,每批 5 个项目。 MoreLinq 包已经为此提供了 Batch 运算符:

    var items=Enumerable.Range(0,17);
    var batches=items.Batch(5);
    
    foreach(var batch in batches)
    {
       Console.WriteLine(String.Join(" - ",batch));
    }
    

    这会产生:

    0 - 1 - 2 - 3 - 4
    5 - 6 - 7 - 8 - 9
    10 - 11 - 12 - 13 - 14
    15 - 16
    

    这比分组要快得多,因为它只迭代集合一次。

    MoreLINQ 也有其他运算符,如 Window、WindowLeft 和 WindowRight,它们会产生值的滑动窗口。 items.Window(5) 会产生:

    0 - 1 - 2 - 3 - 4
    1 - 2 - 3 - 4 - 5
    ...
    11 - 12 - 13 - 14 - 15
    12 - 13 - 14 - 15 - 16
    

    实施

    操作符的实现很简单,你可以把它复制到你的项目中:

    public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(this IEnumerable<TSource> source, int size)
    {
        return Batch(source, size, x => x);
    }
    
    public static IEnumerable<TResult> Batch<TSource, TResult>( IEnumerable<TSource> source, int size,
        Func<IEnumerable<TSource>, TResult> resultSelector)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size));
        if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
    
        return _(); IEnumerable<TResult> _()
        {
            TSource[] bucket = null;
            var count = 0;
    
            foreach (var item in source)
            {
                if (bucket == null)
                {
                    bucket = new TSource[size];
                }
    
                bucket[count++] = item;
    
                // The bucket is fully buffered before it's yielded
                if (count != size)
                {
                    continue;
                }
    
                yield return resultSelector(bucket);
    
                bucket = null;
                count = 0;
            }
    
            // Return the last bucket with all remaining elements
            if (bucket != null && count > 0)
            {
                Array.Resize(ref bucket, count);
                yield return resultSelector(bucket);
            }
        }
    }
    

    代码使用数组来提高效率。如果您真的想使用可变列表,您可以将bucket 的类型更改为List&lt;T&gt;,例如:

    if (bucket == null)
    {
       bucket = new List<TSource>(size); //IMPORTANT: set the capacity to avoid reallocations
    }
    
    bucket.Add(item);
    
    ...
    

    【讨论】:

      【解决方案3】:

      为什么不只是GroupBy

       using System.Linq;
      
       ...
      
       int groupSize = 5;
      
       var result = MyList
         .Select((item, index) => new {
            item,
            index 
          })
         .GroupBy(pair => pair.index / groupSize, 
                  pair => pair.item)
         .Select(group => group.ToList())
         .ToList(); 
      

      【讨论】:

        【解决方案4】:

        如果你有一个项目集合

        var items = Enumerable.Range(1, 20);
        

        你想一次拿 5 个

        var setSize = 5;
        

        您可以按索引遍历集合,一次将 5 个作为列表,然后将所有 5 个列表放入一个外部列表中

         Enumerable.Range(0, items.Count() - setSize).Select(x => items.Skip(x).Take(setSize).ToList()).ToList()
        

        结果(来自 C# 交互式 shell)看起来像

        List<List<int>>(15) { 
            List<int>(5) { 1, 2, 3, 4, 5 }, 
            List<int>(5) { 2, 3, 4, 5, 6 }, 
            List<int>(5) { 3, 4, 5, 6, 7 }, 
            List<int>(5) { 4, 5, 6, 7, 8 }, 
            List<int>(5) { 5, 6, 7, 8, 9 }, 
            List<int>(5) { 6, 7, 8, 9, 10 }, 
            List<int>(5) { 7, 8, 9, 10, 11 }, 
            List<int>(5) { 8, 9, 10, 11, 12 }, 
            List<int>(5) { 9, 10, 11, 12, 13 }, 
            List<int>(5) { 10, 11, 12, 13, 14 }, 
            List<int>(5) { 11, 12, 13, 14, 15 }, 
            List<int>(5) { 12, 13, 14, 15, 16 }, 
            List<int>(5) { 13, 14, 15, 16, 17 }, 
            List<int>(5) { 14, 15, 16, 17, 18 }, 
            List<int>(5) { 15, 16, 17, 18, 19 }
        }
        

        如果您希望每个项目在每个列表中只显示一次,您可以更改上述内容。假设有奇数个元素:

        var items = Enumerable.Range(1, 11);
        

        您想要更改用于索引到您的集合的初始范围。它不会在每个索引上一次取 5 个,而是每次迭代将索引向上跳 5 个。唯一棘手的部分是确保在集合划分您要获取的元素数量时进行处理;你不想最后得到一个空列表。也就是说,这是不正确的:

        Enumerable.Range(0, items.Count() / setSize).Select( // don't do this
        

        那么语句就是

        Enumerable.Range(0, ((items.Count() - 1) / setSize) + 1).Select(x => items.Skip(setSize * x).Take(setSize).ToList()).ToList();
        

        结果(来自 C# 交互式 shell)看起来像

        List<List<int>>(3) {
            List<int>(5) { 1, 2, 3, 4, 5 },
            List<int>(5) { 6, 7, 8, 9, 10 },
            List<int>(1) { 11 }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-26
          • 2015-04-29
          • 1970-01-01
          • 2014-02-11
          • 1970-01-01
          相关资源
          最近更新 更多