17

Is there a slick way to merge multiple Lists into a single List using LINQ to effectively replicate this?

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

Or, since the lists arrive separately, its more effective to merge them sequentially like the following.

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];
}
3
  • possible duplicate of Create Items from 3 collections using Linq Commented Nov 21, 2013 at 17:41
  • Similar, but different. The former is specific to memory performance and resource optimization. This was not a question about resources, the answers are not targeted at a specific performance metric, and provide a broad set of possibilities, unconstrained by performance factors. Commented Nov 25, 2013 at 19:26
  • The other question is poorly written, and the answers aren't as good as the ones here, but it isn't specifically concerned with performance at all (the OP just mentioned that s/he was running out of memory), it's a straightforward and minimal question, and it's older, so therefore I see it as a "possible" duplicate. Either way, others should know that a similar question exists and have a link to it. Commented Nov 26, 2013 at 23:56

8 Answers 8

28

You're essentially trying to zip up three collections. If only the LINQ Zip() method supported zipping up more than two simultaneously. But alas, it only supports only two at a time. But we can make it work:

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

Of course it's not terribly painful to write up an extension method to do three (or more).

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

This makes things a lot more nicer:

var colors =
    reds.Zip(greens, blues, (red, green, blue) =>
        new RGB(red, green, blue)
    )
    .ToList();
Sign up to request clarification or add additional context in comments.

2 Comments

+1 IMHO reds.Zip(greens.Zip(blues, (g, b) => new {g,b}), (r, gb) => new RGB(r, gb.g, gb.b)) would be more readable though.
That'd work too and I'd agree, definitely more readable (though I'd write out the names personally). But this is enough to get my point across.
16

Yes - you can do it like this:

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

1 Comment

It does not work if the lists are from different size. Zip is more reliable in that case.
6

use SelectMany like this:

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

Comments

4

Here is a simplified version which takes any number of sequences (as an array) of the same type and zips them together:

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

Pros

  • any number of sequences
  • four lines of code
  • another overload for LINQ .Zip() method
  • zips all sequences at once instead of chaining .Zip to add one more sequence each time

Cons

  • same type required for all sequences (not a problem in your situation)
  • no checking for same list length (add a line if you need it)

Usage

Zipping colors

Comments

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

3 Comments

The only problem with this solution is that it assumes the length of the red array. It will fail catastrophically if there are more reds than the others.
@JeffMercado Same applies to the OP's solution #1, though.
Yes, care is very important to keep them in sync. Fortunately in this application you've already thrown exceptions if the lists are the wrong size and/or the checksum is wrong. Even if they are the same size the answer ends up terribly wrong even if they're not all in the correct order. Its never pretty trying to put together related, but distinctly separate objects.
1

You can use Aggregate with Zip to zip an arbitrary number of IEnumerables in one go.

Here's how you might do that with your example:

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

Generally, relying on Zip as the underlying combiner avoids problems with varying input lengths.

Comments

0

For what it's worth, I like LINQ and use it frequently, but sometimes the old-fashioned way is the best. Note these examples:

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

The output is:

r1 == r2: True
r1 == r3: True
time1 00:00:00.0100071
time2 00:00:00.0170138
time3 00:00:00.0040028

Of course, the time is barely noticeable in this case (to human perception) so it comes down to preference, but knowing #3 is by far the fastest, I'd tend to use it in critical performance areas where the types were more complex or the enumerables could be large.

Also, note the difference when using 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);

The output:

r1 == r2: True
r1 == r3: True
time1 00:00:00.0280393
time2 00:00:00.0089870
time3 00:00:00.0050041

As expected, the .zip method has to do multiple iterations and becomes the slowest.

Comments

0

Jeff Mercado provides an answer where three sequences are zipped. This can be generalized to any number of sequences with the limitation that all sequences then have to have the same item type.

Here is a generalized zip operator that handles varying input lengths and with suitable error handling and proper disposal of the enumerators:

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

}

The colors can then be computed using this generalized zip operator:

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

The code may not be as elegant as some of the other solutions but a generalized zip operator might be useful in some situations and the code I have provided is efficient because it only iterates each source sequence once.

1 Comment

.Skip(n).First() == .ElementAt(n)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.