san*_*ngh 177

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);
Run Code Online (Sandbox Code Playgroud)

输出继电器

A1
B2
C3
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个答案,因为它显示了当元素数量不匹配时会发生什么,类似于[msdn文档](http://msdn.microsoft.com/en-us/library/vstudio/dd267698(v = VS.100)的.aspx) (38认同)
  • 如果我想让 zip 在一个列表中的元素用完的地方继续怎么办?在这种情况下,较短的列表元素应采用默认值。在这种情况下,输出为 A1、B2、C3、D0、E0。 (2认同)
  • @liang 两个选择:A) 编写您自己的 `Zip` 替代方案。B) 编写一个方法来`yield return`较短列表的每个元素,然后无限期地继续`yield return`ing `default`。(选项 B 要求您提前知道哪个列表较短。) (2认同)

jas*_*son 103

Zip用于将两个序列组合成一个.例如,如果您有序列

1, 2, 3
Run Code Online (Sandbox Code Playgroud)

10, 20, 30
Run Code Online (Sandbox Code Playgroud)

并且您希望序列是每个序列中相同位置的元素相乘得到的结果

10, 40, 90
Run Code Online (Sandbox Code Playgroud)

你可以说

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

它被称为"拉链",因为你认为一个序列作为拉链的左侧,而另一个序列作为拉链的右侧,拉链操作员将两侧拉到一起从牙齿上拉出(适当的序列的元素.

  • 绝对是这里最好的解释. (7认同)
  • 喜欢拉链的例子.这很自然.我最初的印象是,它是否与速度或类似的东西有关,就像你在车上的街道上拉链一样. (2认同)

Jus*_*gan 22

它迭代两个序列,并将它们的元素逐个组合成一个新的序列.因此,您获取序列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
Run Code Online (Sandbox Code Playgroud)

如果你在命令式代码中这样做,你可能会做这样的事情:

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

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

var numbersAndWords = numbers.Select(
                          (num, i) => num + " " + words[i]
                      );
Run Code Online (Sandbox Code Playgroud)

当您将数据分散到简单的类似数组的列表中时,这非常有用,每个列表具有相同的长度和顺序,并且每个列表描述同一组对象的不同属性.Zip帮助您将这些数据组合成一个更连贯的结构.

因此,如果你有一个状态名称数组和另一个缩写数组,你可以将它们整理成一个State类,如下所示:

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


Cod*_*shi 14

不要让这个名字让Zip你失望.它与压缩文件或文件夹(压缩)中的压缩无关.它实际上得名于衣服上的拉链如何工作:衣服上的拉链有两面,每边都有一堆牙齿.当你向一个方向走时,拉链枚举(行进)两侧并通过咬紧牙齿来关闭拉链.当你向另一个方向走时,它会打开牙齿.您要么以开放式或封闭式拉链结束.

与该Zip方法的想法相同.考虑一个我们有两个集合的例子.一个持有字母,另一个持有以该字母开头的食品名称.为了清楚起见,我呼吁他们leftSideOfZipperrightSideOfZipper.这是代码.

var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Run Code Online (Sandbox Code Playgroud)

我们的任务是制作一个集合,其中的水果字母用a :和它的名字分隔.像这样:

A : Apple
B : Banana
C : Coconut
D : Donut
Run Code Online (Sandbox Code Playgroud)

Zip救援.为了跟上我们的拉链术语,我们将调用此结果closedZipper和我们将调用的左拉链项目,我们将调用leftTooth右侧righTooth显而易见的原因:

var closedZipper = leftSideOfZipper
   .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
Run Code Online (Sandbox Code Playgroud)

在上文中,我们列举(行进)拉链的左侧和拉链的右侧并对每个牙齿进行操作.我们正在进行的操作是将左牙齿(食物字母)与:右牙齿(食物名称)连接起来.我们使用此代码执行此操作:

(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Run Code Online (Sandbox Code Playgroud)

最终结果如下:

A : Apple
B : Banana
C : Coconut
D : Donut
Run Code Online (Sandbox Code Playgroud)

最后一个字母E怎么了?

如果你列举(拉)真正的衣服拉链和一侧,左侧或右侧无关紧要,牙齿比另一侧少,会发生什么?拉链将停在那里.该Zip方法将完全相同:一旦到达任何一侧的最后一项,它将停止.在我们的情况下,右侧牙齿较少(食物名称),所以它将停在"甜甜圈".

  • @bacon是的,但那样我就无法使用我的拉链示例了;)我想一旦你弄清楚它像拉链,之后就非常简单了。 (3认同)

Ale*_*ica 10

这里的很多答案都展示了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)
Run Code Online (Sandbox Code Playgroud)

这些连续的对对于查找值之间的第一个差异很有用。例如,连续的对IEnumable<MouseXPosition>可用于产生IEnumerable<MouseXDelta>。类似地,采样bool的值button可以被interpretted到的事件,如NotPressed/ Clicked/ Held/ Released。然后,这些事件可以驱动对委托方法的调用。下面是一个例子:

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) );
    }
}
Run Code Online (Sandbox Code Playgroud)

印刷:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Run Code Online (Sandbox Code Playgroud)


小智 6

我没有在评论部分发布的回复点,但回答相关问题:

如果我希望zip继续在一个列表用完元素的情况下怎么办?在这种情况下,较短的列表元素应采用默认值.在这种情况下输出为A1,B2,C3,D0,E0. - liang 2015年11月19日3:29

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

代码示例:

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);
Run Code Online (Sandbox Code Playgroud)

输出:

A1
B2
C3
D0
E0
Run Code Online (Sandbox Code Playgroud)

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

如果不知道哪个序列将是较短的序列,则可以创建一个函数来激活它:

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()); 
}
Run Code Online (Sandbox Code Playgroud)

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

A1 A1
B2 B2
C3 C3
   D0
   E0
Run Code Online (Sandbox Code Playgroud)

回到原始问题的主要答案,人们可能希望做的另一件有趣的事情(当"拉链"序列的长度不同时)是以这样的方式加入它们以便列表的末尾匹配而不是顶部.这可以通过使用.Skip()"跳过"适当数量的项来实现.

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

输出:

C1
D2
E3
Run Code Online (Sandbox Code Playgroud)


Nov*_*ata 5

正如其他人所说,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);
Run Code Online (Sandbox Code Playgroud)

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