Linq中Enumerable.Zip
扩展方法的用途是什么?
答案 0 :(得分:157)
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
答案 1 :(得分:99)
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”,因为你认为一个序列作为拉链的左侧,而另一个序列作为拉链的右侧,拉链操作员将两侧拉到一起配对牙齿(序列的元素)适当。
答案 2 :(得分:22)
迭代两个序列并将它们的元素逐个组合成一个新序列。因此,您获取序列A的元素,使用序列B中的相应元素对其进行转换,结果形成序列C的元素。
考虑它的一种方法是它与Select
类似,除了不是从单个集合中转换项目,它同时适用于两个集合。
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
如果您要在命令式代码中执行此操作,则可能会执行以下操作:
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
});
}
答案 3 :(得分:13)
不要让名字Zip
让你失望。它与压缩文件或文件夹(压缩)中的压缩无关。它实际上得名于衣服上的拉链如何工作:衣服上的拉链有两面,每边都有一堆牙齿。当你向一个方向走时,拉链枚举(行进)两侧并通过咬紧牙齿来关闭拉链。当你向另一个方向走时,它会打开牙齿。您要么以开放式或封闭式拉链结束。
与Zip
方法的想法相同。考虑一个我们有两个集合的例子。一个持有字母,另一个持有以该字母开头的食品名称。为清楚起见,我称之为leftSideOfZipper
和rightSideOfZipper
。这是代码。
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
救援。为了跟上我们的拉链术语,我们将调用此结果closedZipper
以及我们将调用leftTooth
左侧拉链的项目,右侧我们将调用righTooth
,原因显而易见:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
在上文中,我们列举(行进)拉链的左侧和拉链的右侧,并对每个牙齿进行操作。我们正在进行的操作是将左牙齿(食物字母)与:
和右牙齿(食物名称)连接起来。我们使用此代码执行此操作:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
最终结果如下:
A : Apple
B : Banana
C : Coconut
D : Donut
最后一个字母E发生了什么?
如果你列举(拉)真正的衣服拉链和一侧,左侧或右侧无关紧要,牙齿比另一侧少,会发生什么?拉链将停在那里。 Zip
方法将完全相同:一旦到达任何一方的最后一项,它将停止。在我们的情况下,右侧牙齿较少(食物名称),因此它会停在&#34;甜甜圈&#34;。
答案 4 :(得分: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);
输出:
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
回到原始问题的主要答案,人们可能希望做的另一个有趣的事情(当“拉链”序列的长度不同时)是将它们加入这样一种方式,使列表的 end 匹配而不是顶部。这可以通过使用.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
答案 5 :(得分: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);
timeSegments表示队列中当前或出列的项目(最后一个元素被Zip截断)。 timeSegments.Skip(1)表示队列中的下一个或peek项。 Zip方法将这两者组合成一个具有Next和Current属性的匿名对象。 然后我们使用Where过滤并使用AsParallel()进行更改.ForAll。 当然,最后一点可能只是一个常规的foreach或另一个返回有问题的时间段的Select语句。
答案 6 :(得分:2)
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
答案 7 :(得分:2)
这里的许多答案都证明了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<MouseXPosition>
对可以用来产生IEnumerable<MouseXDelta>
。类似地,bool
的采样button
值可以解释为诸如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) );
}
}
打印:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
答案 8 :(得分:0)
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