What Is the Use of Enumerable.Zip Extension Method in Linq

What is the use of Enumerable.Zip extension method in Linq?

The Zip operator merges the corresponding elements of two sequences using a specified selector function.

var letters= new string[] { "A", "B", "C", "D", "E" };
var numbers= new int[] { 1, 2, 3 };
var q = letters.Zip(numbers, (l, n) => l + n.ToString());
foreach (var s in q)
Console.WriteLine(s);

Ouput

A1
B2
C3

C# LINQ zip function equivalent in query expression

Unfortunately the query syntax doesn't support everything that is available through the method syntax (e.g. Zip() method, self-written extension methods or 3rd party libraries like MoreLinq).

Due to this limitation it is not possible to get this functionality into C# by the user. Instead Microsoft as owner of the language had to implement such an functionality like it is described here.

LINQ Zip all elements

Well you can create your custom Zip extension method:

static IEnumerable<T> Zip<T1, T2, T>(this IEnumerable<T1> first,
IEnumerable<T2> second, Func<T1, T2, T> operation)
{
using (var iter1 = first.GetEnumerator())
using (var iter2 = second.GetEnumerator())
{
while (iter1.MoveNext())
{
if (iter2.MoveNext())
{
yield return operation(iter1.Current, iter2.Current);
}
else
{
yield return operation(iter1.Current, default(T2));
}
}
while (iter2.MoveNext())
{
yield return operation(default(T1), iter2.Current);
}
}
}

The main idea was taken from this post answer. You can test it in dotnetfiddle if you want

How Enumerable.Zip works with LinQ

I think you are looking for SelectMany:

List<int> listToReturn= totals = Totals.Where(x => x.Key == myGuid)
.SelectMany(s => s.Value)
.ToList();

What you are trying to do is flatten all the lists into one sequence and that is what SelectMany extension method do. Zip, in the other side, merges each element of the first sequence with an element that has the same index in the second sequence, in other words, it allows you to project from two sequences of the same length.

Update

Now I understand what you are trying to achieve:

var lists= Totals.Where(x => x.Key == myGuid).Select(e=>Value).ToList();// Select the lists
if(lists.Count>0)
var result= Enumerable.Range(0, lists[0].Count).Select(i=> lists.Sum(l=>l[i]));

The thing with this solution you need to make sure that all the selected lists have the same size. I'm pretty sure there is a more elegant solution but this was that come to my mind now. If I think in something better I will update my answer

Enumerable.Zip to enforce same lengths

I would probably just reimplement Zip the way you want to. It's really pretty simple - the following is trivially adapted from MoreLINQ. You'll want to give it a better name, mind you...

public static IEnumerable<TResult> ZipForceEqual<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");

return ZipForceEqualImpl(first, second, resultSelector);
}

static IEnumerable<TResult> ZipForceEqualImpl<TFirst, TSecond, TResult>(
IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (var e1 = first.GetEnumerator())
using (var e2 = second.GetEnumerator())
{
while (e1.MoveNext())
{
if (e2.MoveNext())
{
yield return resultSelector(e1.Current, e2.Current);
}
else
{
throw new InvalidOperationException("Sequences differed in length");
}
}
if (e2.MoveNext())
{
throw new InvalidOperationException("Sequences differed in length");
}
}
}

Enumerable.Zip alternative solution in Dot Net 3.0

As you know it is not available in .NET 3.5 and older version of it. However there is an implementation of it by Eric Lippert here https://blogs.msdn.microsoft.com/ericlippert/2009/05/07/zip-me-up/:

The code to do so is pretty trivial; if you happen to need this in C# 3.0, I’ve put the source code below.

public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipIterator(first, second, resultSelector);
}

private static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>
(IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}

Update class property inside Enumerable.Zip linq c#

Enumerable.Repeat(a, b) generates a sequence that contains b times a. So you get a sequence that contains three times the same instance (you only instantiate one new MyClass()).

So by the last execution of a.Value = b you set the Value property of your single MyClass instance to 3, and the x in your WriteLine is always the same instance, containing Value = 3.

A better way to create myclasses would be

var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass());

Note that it's often a bad idea to create sequences with side effects. You actually don't use the sequence returned by Zip but use this query only for it's side effects inside your lambda.

I guess in your real application the creation of myclasses and the update via Zip is more separated than in your question. But if not, you could reduce it all to this:

var myclasses = numbers.Select(i => new MyClass { Value = i });

UPDATE

The reason why you now only get 0s as output is the deferred exeuction (again: using functional programming methods like linq only for its side effects is tricky).

The immediate solution is to add ToList to the creation:

var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass()).ToList();

Without that, the classes are instantiated only at this line:

myclasses.Zip(numbers, (a, b) => a.Value = b).ToList(); 

And the created classes are returned as a sequence which is then turned in to a List by ToList(). But you don't use that return value.

So in the next line

myclasses.ToList().ForEach(x => Console.WriteLine(x.Value));

you creat new instances of the classes.

myclasses as you defined it is only a query, not the answer to that query.

So my final proposal:

var myclasses = Enumerable.Range(1, numbers.Count).Select(i => new MyClass());
var updatedClasses = myclasses.Zip(numbers, (a, b) => {a.Value = b; return a;}).ToList();
updatedClasses.ForEach(x => Console.WriteLine(x.Value));

Note that I changed the lambda in Zip to return the instance of the class instead of the int.

Why is the 2-argument overload of Enumerable.Zip() in .NET Core but not in .NET Standard?

.NET Standard is a common library that is supported by both .NET Framework and .NET Core.

Enumerable.Zip only has one overload in .NET Framework, and in fact, the second overload was only introduced in .NET Core 3.0.

.NET Framework isn't actively being updated by Microsoft any more, hence the difference.



Related Topics



Leave a reply



Submit