How to Combine More Than Two Generic Lists in C# Zip

How to combine more than two generic lists in C# Zip?

The most obvious way for me would be to use Zip twice.

For example,

var results = l1.Zip(l2, (x, y) => x + y).Zip(l3, (x, y) => x + y);

would combine (add) the elements of three List<int> objects.

Update:

You could define a new extension method that acts like a Zip with three IEnumerables, like so:

public static class MyFunkyExtensions
{
public static IEnumerable<TResult> ZipThree<T1, T2, T3, TResult>(
this IEnumerable<T1> source,
IEnumerable<T2> second,
IEnumerable<T3> third,
Func<T1, T2, T3, TResult> func)
{
using (var e1 = source.GetEnumerator())
using (var e2 = second.GetEnumerator())
using (var e3 = third.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
yield return func(e1.Current, e2.Current, e3.Current);
}
}
}

The usage (in the same context as above) now becomes:

var results = l1.ZipThree(l2, l3, (x, y, z) => x + y + z);

Similarly, you three lists can now be combined with:

var results = list1.ZipThree(list2, list3, (a, b, c) => new { a, b, c });

Optimizing LINQ combining multiple lists into new generic list

That is what Zip is for.

var result = FirstNames
.Zip(LastNames, (f,l) => new {f,l})
.Zip(BirthDates, (fl, b) => new {First=fl.f, Last = fl.l, BirthDate = b});

Regarding scaling:

int count = 50000000;
var FirstNames = Enumerable.Range(0, count).Select(x=>x.ToString());
var LastNames = Enumerable.Range(0, count).Select(x=>x.ToString());
var BirthDates = Enumerable.Range(0, count).Select(x=> DateTime.Now.AddSeconds(x));

var sw = new Stopwatch();
sw.Start();

var result = FirstNames
.Zip(LastNames, (f,l) => new {f,l})
.Zip(BirthDates, (fl, b) => new {First=fl.f, Last = fl.l, BirthDate = b});

foreach(var r in result)
{
var x = r;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds); // Returns 69191 on my machine.

While these blow up with out of memory:

int count = 50000000;
var FirstNames = Enumerable.Range(0, count).Select(x=>x.ToString());
var LastNames = Enumerable.Range(0, count).Select(x=>x.ToString());
var BirthDates = Enumerable.Range(0, count).Select(x=> DateTime.Now.AddSeconds(x));

var sw = new Stopwatch();
sw.Start();

var FirstNamesList = FirstNames.ToList(); // Blows up in 32-bit .NET with out of Memory
var LastNamesList = LastNames.ToList();
var BirthDatesList = BirthDates.ToList();

var result = Enumerable.Range(0, FirstNamesList.Count())
.Select(i => new
{
First = FirstNamesList[i],
Last = LastNamesList[i],
Birthdate = BirthDatesList[i]
});

result = BirthDatesList.Select((bd, i) => new
{
First = FirstNamesList[i],
Last = LastNamesList[i],
BirthDate = bd
});

foreach(var r in result)
{
var x = r;
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

At lower values, the cost of converting the Enumerables to a List is much more expensive than the additional object creation as well. Zip was approximately 30% faster than the indexed versions. As you add more columns, Zips advantage would likely shrink.

The performance characteristics are also very different. The Zip routine will start outputting answers almost immediately, while the others will start outputting answers only after the entire Enumerables have been read and converted to Lists, so if you take the results and do pagination on it with .Skip(x).Take(y), or check if something exists .Any(...) it will be magnitudes faster as it doesn't have to convert the entire enumerable.

Lastly, if it becomes performance critical, and you need to implement many results, you could consider extending zip to handle an arbitrary number of Enumerables like (shamelessly stolen from Jon Skeet - https://codeblog.jonskeet.uk/2011/01/14/reimplementing-linq-to-objects-part-35-zip/):

private static IEnumerable<TResult> Zip<TFirst, TSecond, TThird, TResult>( 
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
IEnumerable<TThird> third,
Func<TFirst, TSecond, TThird, TResult> resultSelector)
{
using (IEnumerator<TFirst> iterator1 = first.GetEnumerator())
using (IEnumerator<TSecond> iterator2 = second.GetEnumerator())
using (IEnumerator<TThird> iterator3 = third.GetEnumerator())
{
while (iterator1.MoveNext() && iterator2.MoveNext() && iterator3.MoveNext())
{
yield return resultSelector(iterator1.Current, iterator2.Current, iterator3.Current);
}
}
}

Then you can do this:

var result = FirstNames
.Zip(LastNames, BirthDates, (f,l,b) => new {First=f,Last=l,BirthDate=b});

And now you don't even have the issue of the middle object being created, so you get the best of all worlds.

Or use the implementation here to handle any number generically: Zip multiple/abitrary number of enumerables in C#

How to Zip two Lists of different size to create a new list that is same as the size of the longest amongst the original lists?

You can use Concat to make them both the same size, and then zip it:

var zipped = list1.Concat(Enumerable.Repeat(0,Math.Max(list2.Count-list1.Count,0)))
.Zip(list2.Concat(Enumerable.Repeat(0,Math.Max(list1.Count-list2.Count,0))),
(a,b)=>(a,b));

Or create an extension method:

public static class ZipExtension{
public static IEnumerable<TResult> Zip<TFirst,TSecond,TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst,TSecond,TResult> func,
TFirst padder1,
TSecond padder2)
{
var firstExp = first.Concat(
Enumerable.Repeat(
padder1,
Math.Max(second.Count()-first.Count(),0)
)
);
var secExp = second.Concat(
Enumerable.Repeat(
padder2,
Math.Max(first.Count()-second.Count(),0)
)
);
return firstExp.Zip(secExp, (a,b) => func(a,b));
}
}

So you can use like this:

//last 2 arguments are the padder values for list1 and list2
var zipped = list1.Zip(list2, (a,b) => (a,b), 0, 0);

How to combine three arrays in C# Zip?

What you are selecting is

Tuple<Tuple<T1, T2>, T3>

So you'll end up with accesses that look like:

myTuple.Item1.Item2

which is a bit useless.

There is no 3way Zip on the Enumerable class, but there's nothing to stop you writing one:

public static class Zips
{
public static IEnumerable<TResult> Zip3<T1, T2, T3, TResult>(
this IEnumerable<T1> seq1,
IEnumerable<T2> seq2,
IEnumerable<T3> seq3,
Func<T1, T2, T3, TResult> selector) => seq1
.Zip(seq2, (x, y) => (x, y))
.Zip(seq3, (x, y) => (x.x, x.y, y))
.Select(x => selector(x.x, x.Item2, x.Item3));
}

so, now you can:

arr1.Zip3(arr2, arr3, Tuple.Create)

and you'll get out your expected 3-value tuple of type Tuple<T1, T2, T3>

Please consider that for each arity of Zip that you require, you'll need to make a new method. Obviously the complexity of such methods also increases.

Merge or zip different list of objects having different structures into single list

Addition after comment: in the while part of my code I forgot to MoveNext. Repaired. Thanks enigma state for noticing.

So you have three sequences of different items, and you want a method, that returns a sequence containing the same indexed elements of your sequences.

This is a difficult way to say, that you want a sequence like:

A[0] / B[0] / C[0]
A[1] / B[1] / C[1]
A[2] / null / C[2]
etc.

So if one of the sequences runs out of elements, you want to continue enumerating using NULL as value

This method is very similar to Enumerable.Zip, except that you have three sequences, and you want to continue enumerating if one of the sequences is too short.

Whenever you think there is a missing LINQ method, consider writing an extension method for IEnumerable. Especially if you think you can reuse it in other situations

Creating an extension method for an IEnumerable is usually fairly simple. See extension methods demystified

I'll write a generic extension method, that has three input sequences, and a result selector to define the returned output sequence. This result selector is similar to the selector parameter in Enumerable.Select.

public static IEnumerable<TResult> ZipWithNull<T1, T2, T2, TResult>(
this IEnumerable<T1> source1,
IEnumerable<T2> source2,
IEnumerable<T3> source3,
Func<T1, T2, T3, TResult> resultSelector)
{
// get the enumerators and start enumerating until there are no more elements
IEnumerator<T1> enumerator1 = source1.GetEnumerator();
IEnumerator<T2> enumerator2 = source2.GetEnumerator();
IEnumerator<T3> enumerator3 = source3.GetEnumerator();

// check if there is at least one item available
bool t1Available = enumerator1.MoveNext();
bool t2Available = enumerator2.MoveNext();
bool t3Available = enumerator3.MoveNext();
bool anyAvailabe = t1Available || t2Available || t3Available;

while (anyAvailable)
{
// if available use the Current, else use default (= null for classes)
T1 t1 = t1Available ? enumerator1.Current ?? default(T1);
T2 t2 = t2Available ? enumerator2.Current ?? default(T2);
T3 t3 = t3Available ? enumerator3.Current ?? default(T3);

TResult result = resultSelector(t1, t2, t3);
yield return result;

t1Available = enumerator1.MoveNext();
t2Available = enumerator2.MoveNext();
t3Available = enumerator3.MoveNext();
anyAvailabe = t1Available || t2Available || t3Available;
}
}

Usage:

List<A> listA = ...
List<B> listA = ...
List<C> listA = ...

// you want a dash if the value is null
const string dash = "-";

var result = listA.ZipWithNull(listB, listC,

// parameter ResultSelector: taks one a, b, c and create the output:
(a, b, c) => new
{
Number = a?.Number.ToString() ?? dash,
Name = a?.Name ?? dash,
Casted = b?.Casted ?? dash,
Id = c?.Id.ToString() ?? dash,
IsSelect = c?.IsSelect.ToString() ?? dash,
});

Do note that this result selector can be optimized. For instance, whether parameter a equals null is checked twice. With a little more effort you can make sure that every input of the resultSelector is checked only once. For the example I chose simplicity above efficiency.

The nice thing is that you can use ZipWithNull for any sequence that you want to zip with null values as substitute. Because I created ZipWithNull as any other LINQ method, I can intertwine it with other LINQ methods:

var result = customers
.Where(customer => customer.BirthDay.Year >= 2000)
.ZipWithNull(
addresses.Where(address => address.City == "Amsterdam"),
orders.Where(order => order.Total > 1000,
(customer, address, order) => new {...});
.GroupBy(...)
.OrderByDescending(...)
// Etc();

Combining contents of 2 lists in C#

You could use a for-loop and access the lists via index:

for(int i = 0; i < Math.Min(firstNames.Length, lastNames.Length); i++)
{
Console.WriteLine(firstNames[i] + lastNames[i]);
}

better would it be to store the two related information in a common class, for example Avenger with properties FirstName and LastName.

Another way to link two related lists is LINQ's Zip:

var zippedAvengers = firstNames.Zip(lastNames, (f,l) => f + " " + l);
foreach(string name in zippedAvengers)
Console.WriteLine(name);

Merge multiple Lists into one List with LINQ

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, Tuple.Create),
(red, tuple) => new RGB(red, tuple.Item1, tuple.Item2)
)
.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();


Related Topics



Leave a reply



Submit