Linq 中 Enumerable.Zip 扩展方法有什么用?

发布于 2024-10-19 13:23:40 字数 137 浏览 5 评论 0原文

Enumerable.Zip 有什么用Linq 中的扩展方法?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

只想待在家 2024-10-26 13:23:40

Zip 运算符使用指定的选择器函数合并两个序列的相应元素。

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

输出

A1
B2
C3

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
不弃不离 2024-10-26 13:23:40

Zip 用于将两个序列合并为一个。例如,如果您有序列

1, 2, 3

并且

10, 20, 30

您想要将每个序列中相同位置的元素相乘得到的序列,

10, 40, 90

您可以说它

var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);

被称为“zip”,因为您将一个序列视为左侧拉链的一侧,另一个序列作为拉链的右侧,拉链操作员将两侧拉在一起,适当地配对牙齿(序列的元素)。

Zip is for combining two sequences into one. For example, if you have the sequences

1, 2, 3

and

10, 20, 30

and you want the sequence that is the result of multiplying elements in the same position in each sequence to obtain

10, 40, 90

you could say

var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);

It is called "zip" because you think of one sequence as the left-side of a zipper, and the other sequence as the right-side of the zipper, and the zip operator will pull the two sides together pairing off the teeth (the elements of the sequence) appropriately.

り繁华旳梦境 2024-10-26 13:23:40

它迭代两个序列并将它们的元素一一组合成一个新序列。因此,您获取序列 A 的一个元素,将其与序列 B 中的相应元素进行转换,结果形成序列 C 的一个元素。

一种思考方式是,它与 Select 类似,除了它不是转换单个集合中的项目,而是同时适用于两个集合。

来自 有关该方法的 MSDN 文章

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three

如果您要在命令式代码中执行此操作,那么您' d 可能会做这样的事情:

for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
    numbersAndWords.Add(numbers[i] + " " + words[i]);
}

或者如果 LINQ 中没有 Zip ,您可以这样做:

var numbersAndWords = numbers.Select(
                          (num, i) => num + " " + words[i]
                      );

当您将数据分散到简单的、类似数组的列表中(每个列表都有相同的列表)时,这很有用长度和顺序,每个都描述同一组对象的不同属性。 Zip 可帮助您将这些数据片段组合成更连贯的结构。

因此,如果您有一个州名称数组和另一个缩写数组,您可以将它们整理到一个 State 类中,如下所示:

IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
    return stateNames.Zip(statePopulations, 
                          (name, population) => new State()
                          {
                              Name = name,
                              Population = population
                          });
}

It iterates through two sequences and combines their elements, one by one, into a single new sequence. So you take an element of sequence A, transform it with the corresponding element from sequence B, and the result forms an element of sequence C.

One way to think about it is that it's similar to Select, except instead of transforming items from a single collection, it works on two collections at once.

From the MSDN article on the method:

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three

If you were to do this in imperative code, you'd probably do something like this:

for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
    numbersAndWords.Add(numbers[i] + " " + words[i]);
}

Or if LINQ didn't have Zip in it, you could do this:

var numbersAndWords = numbers.Select(
                          (num, i) => num + " " + words[i]
                      );

This is useful when you have data spread into simple, array-like lists, each with the same length and order, and each describing a different property of the same set of objects. Zip helps you knit those pieces of data together into a more coherent structure.

So if you have an array of state names and another array of their abbreviations, you could collate them into a State class like so:

IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
    return stateNames.Zip(statePopulations, 
                          (name, population) => new State()
                          {
                              Name = name,
                              Population = population
                          });
}
有深☉意 2024-10-26 13:23:40

不要让 Zip 这个名字让您失望。它与压缩文件或文件夹(压缩)中的压缩无关。它实际上得名于衣服上的拉链的工作原理:衣服上的拉链有两侧,每侧都有一串齿。当你朝一个方向走时,拉链会枚举(行进)两侧,并通过咬紧牙齿来关闭拉链。当你朝另一个方向走时,它就会张开牙齿。您可以选择打开或关闭的拉链。

这与 Zip 方法的想法相同。考虑一个例子,我们有两个集合。一个保存着字母,另一个保存着以该字母开头的食品名称。为了清楚起见,我将它们称为 leftSideOfZipperrightSideOfZipper。这是代码。

var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };

我们的任务是生成一个集合,其中包含由 : 分隔的水果字母及其名称。就像这样:

A : Apple
B : Banana
C : Coconut
D : Donut

Zip来救援。为了跟上我们的拉链术语,我们将此结果称为 closeZipper,左侧拉链的项目我们将称为 leftTooth,右侧拉链的项目我们将称为 rightTooth< /code> 原因显而易见:

var closedZipper = leftSideOfZipper
   .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();

在上面,我们枚举(遍历)拉链的左侧和拉链的右侧,并对每个齿执行操作。我们正在执行的操作是将左齿(食物字母)与 : 连接起来,然后连接右齿(食物名称)。我们使用以下代码来做到这一点:

(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)

最终结果是这样的:

A : Apple
B : Banana
C : Coconut
D : Donut

最后一个字母 E 发生了什么?

如果您正在枚举(拉动)一条真正的衣服拉链,并且无论左侧还是右侧,一侧的齿数都比另一侧少,会发生什么?那么拉链就会停在那里。 Zip 方法将执行完全相同的操作:一旦到达任一侧的最后一项,它将停止。在我们的例子中,右侧的牙齿较少(食物名称),因此它将停在“甜甜圈”处。

DO NOT let the name Zip throw you off. It has nothing to do with zipping as in zipping a file or a folder (compressing). It actually gets its name from how a zipper on clothes works: The zipper on clothes has 2 sides and each side has a bunch of teeth. When you go in one direction, the zipper enumerates (travels) both sides and closes the zipper by clenching the teeth. When you go in the other direction it opens the teeth. You either end with an open or closed zipper.

It is the same idea with the Zip method. Consider an example where we have two collections. One holds letters and the other holds the name of a food item which starts with that letter. For clarity purposes I am calling them leftSideOfZipper and rightSideOfZipper. Here is the code.

var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };

Our task is to produce one collection which has the letter of the fruit separated by a : and its name. Like this:

A : Apple
B : Banana
C : Coconut
D : Donut

Zip to the rescue. To keep up with our zipper terminology we will call this result closedZipper and the items of the left zipper we will call leftTooth and the right side we will call righTooth for obvious reasons:

var closedZipper = leftSideOfZipper
   .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();

In the above we are enumerating (travelling) the left side of the zipper and the right side of the zipper and performing an operation on each tooth. The operation we are performing is concatenating the left tooth (food letter) with a : and then the right tooth (food name). We do that using this code:

(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)

The end result is this:

A : Apple
B : Banana
C : Coconut
D : Donut

What happened to the last letter E?

If you are enumerating (pulling) a real clothes zipper and one side, does not matter the left side or the right side, has less teeth than the other side, what will happen? Well the zipper will stop there. The Zip method will do exactly the same: It will stop once it has reached the last item on either side. In our case the right side has less teeth (food names) so it will stop at "Donut".

断念 2024-10-26 13:23:40

这里的很多答案都演示了 Zip,但没有真正解释可以激发 Zip 使用的现实生活用例。

Zip 是一种特别常见的模式,非常适合迭代连续的事物对。这是通过迭代可枚举的 X 自身并跳过 1 个元素来完成的:x.Zip(x.Skip(1))。视觉示例:

 x | x.Skip(1) | x.Zip(x.Skip(1), ...)
---+-----------+----------------------
   |    1      |
 1 |    2      | (1, 2)
 2 |    3      | (2, 1)
 3 |    4      | (3, 2)
 4 |    5      | (4, 3)

这些连续的对对于查找值之间的第一个差异很有用。例如,可以使用连续的IEnumable对来产生IEnumerable。同样,button 的采样 bool 值可以解释为 NotPressed/Clicked/Held 等事件/已发布。然后,这些事件可以驱动对委托方法的调用。这是一个例子:

using System;
using System.Collections.Generic;
using System.Linq;

enum MouseEvent { NotPressed, Clicked, Held, Released }

public class Program {
    public static void Main() {
        // Example: Sampling the boolean state of a mouse button
        List<bool> mouseStates = new List<bool> { false, false, false, false, true, true, true, false, true, false, false, true };

        mouseStates.Zip(mouseStates.Skip(1), (oldMouseState, newMouseState) => {
            if (oldMouseState) {
                if (newMouseState) return MouseEvent.Held;
                else return MouseEvent.Released;
            } else {
                if (newMouseState) return MouseEvent.Clicked;
                else return MouseEvent.NotPressed;
            }
        })
        .ToList()
        .ForEach(mouseEvent => Console.WriteLine(mouseEvent) );
    }
}

打印:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked

A lot of the answers here demonstrate Zip, but without really explaining a real life use-case that would motivate the use of Zip.

One particularly common pattern that Zip is fantastic for iterating over successive pairs of things. This is done by iterating an enumerable X with itself, skipping 1 element: x.Zip(x.Skip(1)). Visual Example:

 x | x.Skip(1) | x.Zip(x.Skip(1), ...)
---+-----------+----------------------
   |    1      |
 1 |    2      | (1, 2)
 2 |    3      | (2, 1)
 3 |    4      | (3, 2)
 4 |    5      | (4, 3)

These successive pairs are useful for finding the first differences between values. For example, successive pairs of IEnumable<MouseXPosition> can be used to produce IEnumerable<MouseXDelta>. Similarly, sampled bool values of a button can be interpretted into events like NotPressed/Clicked/Held/Released. Those events can then drive calls to delegate methods. Here's an example:

using System;
using System.Collections.Generic;
using System.Linq;

enum MouseEvent { NotPressed, Clicked, Held, Released }

public class Program {
    public static void Main() {
        // Example: Sampling the boolean state of a mouse button
        List<bool> mouseStates = new List<bool> { false, false, false, false, true, true, true, false, true, false, false, true };

        mouseStates.Zip(mouseStates.Skip(1), (oldMouseState, newMouseState) => {
            if (oldMouseState) {
                if (newMouseState) return MouseEvent.Held;
                else return MouseEvent.Released;
            } else {
                if (newMouseState) return MouseEvent.Clicked;
                else return MouseEvent.NotPressed;
            }
        })
        .ToList()
        .ForEach(mouseEvent => Console.WriteLine(mouseEvent) );
    }
}

Prints:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
简单 2024-10-26 13:23:40

我没有代表点可以在评论部分发布,但可以回答相关问题:

如果我希望 zip 在一个列表用完元素的情况下继续,该怎么办?在
在这种情况下,较短的列表元素应采用默认值。输出
在本例中为 A1、B2、C3、D0、E0。 – 梁 2015-11-19 3:29

您要做的是使用 Array.Resize() 用默认值填充较短的序列,然后将它们 Zip() 在一起。

代码示例:

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

输出:

A1
B2
C3
D0
E0

请注意,使用 Array.Resize() 有一个警告Redim Preserve in C#?

如果不知道哪个序列较短,可以创建一个函数来支持它:

static void Main(string[] args)
{
    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()).ToArray();
    var qDef = ZipDefault(letters, numbers);
    Array.Resize(ref q, qDef.Count());
    // Note: using a second .Zip() to show the results side-by-side
    foreach (var s in q.Zip(qDef, (a, b) => string.Format("{0, 2} {1, 2}", a, b)))
        Console.WriteLine(s);
}

static IEnumerable<string> ZipDefault(string[] letters, int[] numbers)
{
    switch (letters.Length.CompareTo(numbers.Length))
    {
        case -1: Array.Resize(ref letters, numbers.Length); break;
        case 0: goto default;
        case 1: Array.Resize(ref numbers, letters.Length); break;
        default: break;
    }
    return letters.Zip(numbers, (l, n) => l + n.ToString()); 
}

普通 .Zip() 和 ZipDefault() 的输出:

A1 A1
B2 B2
C3 C3
   D0
   E0

返回到原始问题的主要答案,人们可能希望做的另一件有趣的事情(当要“压缩”的序列的长度不同时)是以这样的方式连接它们,以便结束 列表匹配而不是顶部。这可以通过使用 .Skip()“跳过”适当数量的项目来完成。

foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);

输出:

C1
D2
E3

I don't have the rep points to post in the comments section, but to answer the related question :

What if I want zip to continue where one list run out of elements? In
which case the shorter list element should take default value. Output
in this case to be A1, B2, C3, D0, E0. – liang Nov 19 '15 at 3:29

What you would do is to use Array.Resize() to pad-out the shorter sequence with default values, and then Zip() them together.

Code example :

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

Output:

A1
B2
C3
D0
E0

Please note that using Array.Resize() has a caveat : Redim Preserve in C#?

If it is unknown which sequence will be the shorter one, a function can be created that susses it:

static void Main(string[] args)
{
    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()).ToArray();
    var qDef = ZipDefault(letters, numbers);
    Array.Resize(ref q, qDef.Count());
    // Note: using a second .Zip() to show the results side-by-side
    foreach (var s in q.Zip(qDef, (a, b) => string.Format("{0, 2} {1, 2}", a, b)))
        Console.WriteLine(s);
}

static IEnumerable<string> ZipDefault(string[] letters, int[] numbers)
{
    switch (letters.Length.CompareTo(numbers.Length))
    {
        case -1: Array.Resize(ref letters, numbers.Length); break;
        case 0: goto default;
        case 1: Array.Resize(ref numbers, letters.Length); break;
        default: break;
    }
    return letters.Zip(numbers, (l, n) => l + n.ToString()); 
}

Output of plain .Zip() alongside ZipDefault() :

A1 A1
B2 B2
C3 C3
   D0
   E0

Going back to the main answer of the original question, another interesting thing that one might wish to do (when the lengths of the sequences to be "zipped" are different) is to join them in such a way so that the end of the list matches instead of the top. This can be accomplished by "skipping" the appropriate number of items using .Skip().

foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);

Output:

C1
D2
E3
超可爱的懒熊 2024-10-26 13:23:40

正如其他人所说,Zip 允许您组合两个集合以在进一步的 Linq 语句或 foreach 循环中使用。

过去需要 for 循环和两个数组的操作现在可以使用匿名对象在 foreach 循环中完成。

我刚刚发现的一个例子,有点愚蠢,但如果并行化有益的话可能会很有用,那就是具有副作用的单行队列遍历:

timeSegments
    .Zip(timeSegments.Skip(1), (Current, Next) => new {Current, Next})
    .Where(zip => zip.Current.EndTime > zip.Next.StartTime)
    .AsParallel()
    .ForAll(zip => zip.Current.EndTime = zip.Next.StartTime);

timeSegments 表示队列中当前或出队的项目(最后一个元素被 Zip 截断) 。
timeSegments.Skip(1) 表示队列中的下一个或查看项目。
Zip 方法将这两者组合成一个具有 Next 和 Current 属性的匿名对象。
然后我们使用Where 进行过滤并使用AsParallel().ForAll 进行更改。
当然,最后一位可能只是一个常规的 foreach 或另一个返回违规时间段的 Select 语句。

As others have stated, Zip lets you combine two collections for use in further Linq statements or a foreach loop.

Operations that used to require a for loop and two arrays can now be done in a foreach loop using an anonymous object.

An example I just discovered, that is kind of silly, but could be useful if parallelization were beneficial would be a single line Queue traversal with side effects:

timeSegments
    .Zip(timeSegments.Skip(1), (Current, Next) => new {Current, Next})
    .Where(zip => zip.Current.EndTime > zip.Next.StartTime)
    .AsParallel()
    .ForAll(zip => zip.Current.EndTime = zip.Next.StartTime);

timeSegments represents the current or dequeued items in a queue (the last element is truncated by Zip).
timeSegments.Skip(1) represents the next or peek items in a queue.
The Zip method combines these two into a single anonymous object with a Next and Current property.
Then we filter with Where and make changes with AsParallel().ForAll.
Of course the last bit could just be a regular foreach or another Select statement that returns the offending time segments.

老旧海报 2024-10-26 13:23:40

Zip 方法允许您(调用者)使用合并函数提供程序“合并”两个不相关的序列。 MSDN 上的示例实际上很好地演示了您可以使用 Zip 做什么。在此示例中,您采用两个任意的、不相关的序列,并使用任意函数将它们组合起来(在本例中,只需将两个序列中的项目连接到单个字符串中)。

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three

The Zip method allows you to "merge" two unrelated sequences, using a merging function provider by you, the caller. The example on MSDN is actually pretty good at demonstrating what you can do with Zip. In this example, you take two arbitrary, unrelated sequences, and combine them using an arbitrary function (in this case, just concatenating items from both sequences into a single string).

int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);

foreach (var item in numbersAndWords)
    Console.WriteLine(item);

// This code produces the following output:

// 1 one
// 2 two
// 3 three
南薇 2024-10-26 13:23:40
string[] fname = { "mark", "john", "joseph" };
string[] lname = { "castro", "cruz", "lopez" };

var fullName = fname.Zip(lname, (f, l) => f + " " + l);

foreach (var item in fullName)
{
    Console.WriteLine(item);
}
// The output are

//mark castro..etc
string[] fname = { "mark", "john", "joseph" };
string[] lname = { "castro", "cruz", "lopez" };

var fullName = fname.Zip(lname, (f, l) => f + " " + l);

foreach (var item in fullName)
{
    Console.WriteLine(item);
}
// The output are

//mark castro..etc
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文