使用LINQ将多个列表合并为一个列表

rdlzhqv9  于 2023-02-01  发布在  其他
关注(0)|答案(8)|浏览(313)

有没有一种巧妙的方法可以使用LINQ将多个List合并为一个List来有效地复制它?

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

pqwbnv8z1#

您实际上是在尝试压缩三个集合。如果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), (red, tuple) =>
        new RGB(red, tuple.First, tuple.Second)
    )
    .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();
y3bcpkx1

y3bcpkx12#

是的-你可以这样做:

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();
icnyk63a

icnyk63a3#

按如下方式使用SelectMany:

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

62o28rlo4#

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

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以便每次再添加一个序列

缺点

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

用法

yiytaume

yiytaume5#

var colours = red.Select((t, i) => new RGB(t, green[i], blue[i])).ToList();
ohtdti5x

ohtdti5x6#

可以使用Aggregate with Zip一次性压缩任意数量的IEnumerables。
下面是您可以如何在示例中执行此操作:

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作为基础组合器避免了输入长度变化的问题。

2fjabf4q

2fjabf4q7#

不管怎样,我喜欢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
时间3 00时00分00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒00秒
当然,在这种情况下,时间几乎不明显(对人类感知来说),所以这取决于偏好,但由于知道#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
时间2 00:00:0089870
时间3 00:00:0050041
正如预期的那样,. zip方法必须进行多次迭代,因此速度最慢。

ltqd579y

ltqd579y8#

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运算符可能有用,并且我提供的代码是高效的,因为它只对每个源序列迭代一次。

相关问题