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 IEnumerable
s, 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 });
There is another quite interesting solution that I'm aware of. It's interesting mostly from educational perspective but if one needs to perform zipping different counts of lists A LOT, then it also might be useful.
This method overrides .NET's LINQ SelectMany
function which is taken by a convention when you use LINQ's query syntax. The standard SelectMany
implementation does a Cartesian Product. The overrided one can do zipping instead. The actual implementation could be:
static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> selector, Func<TSource, TCollection, TResult> select)
{
using (var e1 = source.GetEnumerator())
using (var e2 = selector(default(TSource)).GetEnumerator())
while (true)
if (e1.MoveNext() && e2.MoveNext())
yield return select(e1.Current, e2.Current);
else
yield break;
}
It looks a bit scary but it is a logic of zipping which if written once, can be used in many places and the client's code look pretty nice - you can zip any number of IEnumerable<T>
using standard LINQ query syntax:
var titles = new string[] { "Analyst", "Consultant", "Supervisor"};
var names = new string[] { "Adam", "Eve", "Michelle" };
var surnames = new string[] { "First", "Second", "Third" };
var results =
from title in titles
from name in names
from surname in surnames
select $"{ title } { name } { surname }";
If you then execute:
foreach (var result in results)
Console.WriteLine(result);
You will get:
Analyst Adam First
Consultant Eve Second
Supervisor Michelle Third
You should keep this extension private within your class because otherwise you will radically change behavior of surrounding code. Also, a new type will be useful so that it won't colide with standard LINQ behavior for IEnumerables.
For educational purposes I've created once a small c# project with this extension method + few benefits: https://github.com/lukiasz/Zippable
Also, if you find this interesting, I strongly recommend Jon Skeet's Reimplementing LINQ to Objects articles.
Have fun!
You can combine many lists in C# with cascade zip methods and anonymous classes and Tuple result.
List<string> list1 = new List<string> { "test", "otherTest" };
List<string> list2 = new List<string> { "item", "otherItem" };
List<string> list3 = new List<string> { "value", "otherValue" };
IEnumerable<Tuple<string, string, string>> result = list1
.Zip(list2, (e1, e2) => new {e1, e2})
.Zip(list3, (z1, e3) => Tuple.Create(z1.e1, z1.e2, e3));
The result is:
[0]
{(test, item, value)}
Item1: "test"
Item2: "item"
Item3: "value"
It is one of those cases where we need to decide if to favor code with better readability vs. shorter code with Linq, I preferred code readability.
class Program
{
static void Main(string[] args)
{
List<string> list1 = new List<string> { "test", "otherTest" };
List<string> list2 = new List<string> { "item", "otherItem" };
List<string> list3 = new List<string> { "value", "otherValue" };
var result = CombineListsByLayers(list1, list2, list3);
}
public static List<string>[] CombineListsByLayers(params List<string>[] sourceLists)
{
var results = new List<string>[sourceLists[0].Count];
for (var i = 0; i < results.Length; i++)
{
results[i] = new List<string>();
foreach (var sourceList in sourceLists)
results[i].Add(sourceList[i]);
}
return results;
}
Generic solution for any number of lists of different sizes to zip:
public static IEnumerable<TItem> ZipAll<TItem>(this IReadOnlyCollection<IEnumerable<TItem>> enumerables)
{
var enumerators = enumerables.Select(enumerable => enumerable.GetEnumerator()).ToList();
bool anyHit;
do
{
anyHit = false;
foreach (var enumerator in enumerators.Where(enumerator => enumerator.MoveNext()))
{
anyHit = true;
yield return enumerator.Current;
}
} while (anyHit);
foreach (var enumerator in enumerators)
{
enumerator.Dispose();
}
}