Reputation: 933
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];
}
Upvotes: 17
Views: 36546
Reputation: 134811
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();
Upvotes: 28
Reputation: 106796
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.
Upvotes: 0
Reputation: 61
use SelectMany like this:
List_A.Select(a => a.List_B).SelectMany(s => s).ToList();
Upvotes: 6
Reputation: 2026
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());
}
.Zip()
method.Zip
to add one more sequence each timeUpvotes: 4
Reputation: 1
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.
Upvotes: 0
Reputation: 2191
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.
Upvotes: 1
Reputation: 726479
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();
Upvotes: 16
Reputation: 6438
var colours = red.Select((t, i) => new RGB(t, green[i], blue[i])).ToList();
Upvotes: 3