【问题标题】:Merge multiple Lists into one List with LINQ使用 LINQ 将多个列表合并为一个列表
【发布时间】:2013-02-01 03:56:30
【问题描述】:

有没有一种巧妙的方法可以使用 LINQ 将多个列表合并到一个列表中以有效地复制它?

public class RGB
{
    public int Red { get; set; }
    public int Green { get; set; }
    public int Blue { get; set; }
    public RGB(int red, int green, int blue) { Red = red; Green = green; Blue = blue; }
}

public void myFunction()
{
    List<int> red = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };
    List<int> green = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };
    List<int> blue = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };

    List<RGB> colors = new List<RGB>();

    colors.Add(new RGB(red[0], green[0], blue[0]));
    colors.Add(new RGB(red[1], green[1], blue[1]));
    colors.Add(new RGB(red[2], green[2], blue[2]));
    colors.Add(new RGB(red[3], green[3], blue[3]));
    colors.Add(new RGB(red[4], green[4], blue[4]));
}

或者,由于列表是分开到达的,按如下顺序合并它们会更有效。

public class RGB
{
    public int Red { get; set; }
    public int Green { get; set; }
    public int Blue { get; set; }

    public RGB(int red, int green, int blue) { Red = red; Green = green; Blue = blue; }
}

public void myFunction()
{
    List<int> red = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };

    List<RGB> colors = new List<RGB>();

    colors.Add(new RGB(red[0], 0, 0));
    colors.Add(new RGB(red[1], 0, 0));
    colors.Add(new RGB(red[2], 0, 0));
    colors.Add(new RGB(red[3], 0, 0));
    colors.Add(new RGB(red[4], 0, 0));

    List<int> green = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };

    colors[0].Green = green[0];
    colors[1].Green = green[1];
    colors[2].Green = green[2];
    colors[3].Green = green[3];
    colors[4].Green = green[4];

    List<int> blue = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };

    colors[0].Blue = blue[0];
    colors[1].Blue = blue[1];
    colors[2].Blue = blue[2];
    colors[3].Blue = blue[3];
    colors[4].Blue = blue[4];
}

【问题讨论】:

  • 相似,但不同。前者专门针对内存性能和资源优化。这不是关于资源的问题,答案不是针对特定的性能指标,而是提供了广泛的可能性,不受性能因素的限制。
  • 另一个问题写得不好,答案不如这里的好,但它根本不特别关心性能(OP刚刚提到他/她正在跑步内存不足),这是一个简单且最小的问题,而且它较旧,因此我将其视为“可能的”重复。无论哪种方式,其他人都应该知道存在类似的问题并具有指向它的链接。

标签: c# linq design-patterns


【解决方案1】:

是的 - 你可以这样做:

List<int> red = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };
List<int> green = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };
List<int> blue = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };

List<RGB> colors = Enumerable
    .Range(0, red.Count)
    .Select(i => new RGB(red[i], green[i], blue[i]))
    .ToList();

【讨论】:

  • 如果列表大小不同,它就不起作用。在这种情况下,Zip 更可靠。
【解决方案2】:

像这样使用 SelectMany:

List_A.Select(a => a.List_B).SelectMany(s => s).ToList();

【讨论】:

    【解决方案3】:

    这是一个简化版本,它采用 任意 个相同类型的序列(作为一个数组)并将它们压缩在一起:

    public static IEnumerable<TResult> Zip<T, TResult>(this IEnumerable<T>[] sequences, Func<T[], TResult> resultSelector)
    {
        var enumerators = sequences.Select(s => s.GetEnumerator()).ToArray();
        while(enumerators.All(e => e.MoveNext()))
            yield return resultSelector(enumerators.Select(e => e.Current).ToArray());
    }
    

    优点

    • 任意数量的序列
    • 四行代码
    • LINQ .Zip() 方法的另一个重载
    • 一次压缩所有序列,而不是链接 .Zip 以每次添加一个序列

    缺点

    • 所有序列都需要相同的类型(在您的情况下不是问题)
    • 不检查相同的列表长度(如果需要,添加一行)

    用法

    【讨论】:

      【解决方案4】:

      您实际上是在尝试压缩三个集合。如果只有 LINQ Zip() 方法支持同时压缩两个以上。但是很可惜,它一次只支持两个。但我们可以让它发挥作用:

      var reds = new List<int> { 0x00, 0x03, 0x06, 0x08, 0x09 };
      var greens = new List<int> { 0x00, 0x05, 0x06, 0x07, 0x0a };
      var blues = new List<int> { 0x00, 0x02, 0x03, 0x05, 0x09 };
      
      var colors =
          reds.Zip(greens.Zip(blues, Tuple.Create),
              (red, tuple) => new RGB(red, tuple.Item1, tuple.Item2)
          )
          .ToList();
      

      当然,写一个扩展方法来做三个(或更多)并不是很痛苦。

      public static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>(
          this IEnumerable<TFirst> first,
          IEnumerable<TSecond> second,
          IEnumerable<TThird> third,
          Func<TFirst, TSecond, TThird, TResult> resultSelector)
      {
          using (var enum1 = first.GetEnumerator())
          using (var enum2 = second.GetEnumerator())
          using (var enum3 = third.GetEnumerator())
          {
              while (enum1.MoveNext() && enum2.MoveNext() && enum3.MoveNext())
              {
                  yield return resultSelector(
                      enum1.Current,
                      enum2.Current,
                      enum3.Current);
              }
          }
      }
      

      这让事情变得更好:

      var colors =
          reds.Zip(greens, blues,
              (red, green, blue) => new RGB(red, green, blue)
          )
          .ToList();
      

      【讨论】:

      • +1 恕我直言 reds.Zip(greens.Zip(blues, (g, b) =&gt; new {g,b}), (r, gb) =&gt; new RGB(r, gb.g, gb.b)) 会更具可读性。
      • 这也行,我同意,肯定更易读(虽然我会亲自写出名字)。但这足以说明我的观点。
      【解决方案5】:
      var colours = red.Select((t, i) => new RGB(t, green[i], blue[i])).ToList();
      

      【讨论】:

      • 这个解决方案的唯一问题是它假定red 数组的长度。如果红色比其他红色多,它将灾难性地失败。
      • @JeffMercado 同样适用于 OP 的解决方案 #1。
      • 是的,保持它们同步非常重要。幸运的是,在这个应用程序中,如果列表大小错误和/或校验和错误,您已经抛出异常。即使它们的大小相同,即使它们的顺序不正确,答案也会非常错误。将相关但明显独立的对象放在一起从来都不是一件好事。
      【解决方案6】:

      您可以使用 Aggregate 和 Zip 一次性压缩任意数量的 IEnumerable。

      以下是您可以使用示例执行此操作的方法:

      var colorLists = new List<int>[] { red, green, blue };
      var rgbCount = red.Count;
      var emptyTriples =
          Enumerable.Repeat<Func<List<int>>>(() => new List<int>(), rgbCount)
          .Select(makeList => makeList());
      
      var rgbTriples = colorLists.Aggregate(
          emptyTriples,
          (partialTriples, channelValues) =>
              partialTriples.Zip(
                  channelValues,
                  (partialTriple, channelValue) =>
                  {
                      partialTriple.Add(channelValue);
                      return partialTriple;
                  }));
      
      var rgbObjects = rgbTriples.Select(
          triple => new RGB(triple[0], triple[1], triple[2]));
      

      通常,将 Zip 作为底层组合器可以避免输入长度变化的问题。

      【讨论】:

        【解决方案7】:

        对于它的价值,我喜欢 LINQ 并经常使用它,但有时老式的方式是最好的。请注意以下示例:

                const int Max = 100000;
                var rnd = new Random();
                var list1 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
                var list2 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
        
                DateTime start;
        
                start = DateTime.Now;
                var r1 = list1.Zip(list2, (a, b) => new { a, b }).ToList();
                var time1 = DateTime.Now - start;
        
                start = DateTime.Now;
                var r2 = list1.Select((l1, i) => new { a = l1, b = list2[i]}).ToList();
                var time2 = DateTime.Now - start;
        
                start = DateTime.Now;
                var r3 = new int[0].Select(i => new { a = 0, b = 0 }).ToList();
                //  Easy out-of-bounds prevention not offered in solution #2 (if list2 has fewer items)
                int max = Math.Max(list1.Count, list2.Count);
                for (int i = 0; i < max; i++)
                    r3.Add(new { a = list1[i], b = list2[i] });
                var time3 = DateTime.Now - start;
        
                Debug.WriteLine("r1 == r2: {0}", r1.SequenceEqual(r2));
                Debug.WriteLine("r1 == r3: {0}", r1.SequenceEqual(r3));
                Debug.WriteLine("time1 {0}", time1);
                Debug.WriteLine("time2 {0}", time2);
                Debug.WriteLine("time3 {0}", time3);
        

        输出是:

        r1 == r2: 真
        r1 == r3: 真
        时间 1 00:00:00.0100071
        时间2 00:00:00.0170138
        time3 00:00:00.0040028

        当然,在这种情况下(对于人类的感知)时间几乎不明显,所以它归结为偏好,但知道 #3 是迄今为止最快的,我倾向于在关键性能领域使用它,其中类型更复杂或可枚举量可能很大。

        另外,请注意使用 3 时的区别:

                const int Max = 100000;
                var rnd = new Random();
                var list1 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
                var list2 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
                var list3 = Enumerable.Range(1, Max).Select(r => rnd.Next(Max)).ToList();
        
                DateTime start;
        
                start = DateTime.Now;
                var r1 = list1.Zip(list2, (a, b) => new { a, b }).Zip(list3, (ab, c) => new { ab.a, ab.b, c }).ToList();
                var time1 = DateTime.Now - start;
        
                start = DateTime.Now;
                var r2 = list1.Select((l1, i) => new { a = l1, b = list2[i], c = list3[i] }).ToList();
                var time2 = DateTime.Now - start;
        
                start = DateTime.Now;
                var r3 = new int[0].Select(i => new { a = 0, b = 0, c = 0 }).ToList();
                //  Easy out-of-bounds prevention not offered in solution #2 (if list2 or list3 have fewer items)
                int max = new int[] { list1.Count, list2.Count, list3.Count }.Max();
                for (int i = 0; i < max; i++)
                    r3.Add(new { a = list1[i], b = list2[i], c = list3[i] });
                var time3 = DateTime.Now - start;
        
                Debug.WriteLine("r1 == r2: {0}", r1.SequenceEqual(r2));
                Debug.WriteLine("r1 == r3: {0}", r1.SequenceEqual(r3));
                Debug.WriteLine("time1 {0}", time1);
                Debug.WriteLine("time2 {0}", time2);
                Debug.WriteLine("time3 {0}", time3);
        

        输出:

        r1 == r2: 真
        r1 == r3: 真
        时间 1 00:00:00.0280393
        time2 00:00:00.0089870
        time3 00:00:00.0050041

        正如预期的那样,.zip 方法必须进行多次迭代并且变得最慢。

        【讨论】:

          【解决方案8】:

          Jeff Mercado 提供了一个答案,其中三个序列被压缩。这可以推广到任意数量的序列,但所有序列都必须具有相同的项目类型。

          这是一个通用的 zip 运算符,它处理不同的输入长度,并具有适当的错误处理和适当的枚举数处理:

          static class EnumerableExtensions {
          
            public static IEnumerable<TResult> Zip<TSource, TResult>(
              this IEnumerable<IEnumerable<TSource>> source,
              Func<IEnumerable<TSource>, TResult> resultSelector
            ) {
              if (source == null)
                throw new ArgumentNullException("source");
              if (resultSelector == null)
                throw new ArgumentNullException("resultSelector");
          
              var enumerators = new List<IEnumerator<TSource>>();
              try {
                foreach (var enumerable in source) {
                  if (enumerable == null)
                    throw new ArgumentNullException();
                  enumerators.Add(enumerable.GetEnumerator());
                }
          
                while (enumerators.Aggregate(true, (moveNext, enumerator) => moveNext && enumerator.MoveNext()))
                  yield return resultSelector(enumerators.Select(enumerator => enumerator.Current));
              }
              finally {
                foreach (var enumerator in enumerators)
                  enumerator.Dispose();
              }
            }
          
          }
          

          然后可以使用这个广义的 zip 运算符计算颜色:

          var reds = new[] { 0x00, 0x03, 0x06, 0x08, 0x09 };
          var greens = new[] { 0x00, 0x05, 0x06, 0x07, 0x0a };
          var blues = new[] { 0x00, 0x02, 0x03, 0x05, 0x09 };
          var colors = new[] { reds, greens, blues }
            .Zip(rgb => new RGB(rgb.First(), rgb.Skip(1).First(), rgb.Skip(2).First()));
          

          代码可能不像其他一些解决方案那样优雅,但通用的 zip 运算符在某些情况下可能很有用,而且我提供的代码很高效,因为它只迭代每个源序列一次。

          【讨论】:

          • .Skip(n).First() == .ElementAt(n)
          猜你喜欢
          • 2013-08-22
          • 1970-01-01
          • 2021-06-02
          • 1970-01-01
          • 2020-07-19
          • 2022-01-07
          • 1970-01-01
          • 2014-01-27
          相关资源
          最近更新 更多