Enumerable.Zip
是Linq中的扩展方法,用于将两个序列合并成一个序列。
Enumerable.Zip
是Linq中的扩展方法,用于将两个序列合并成一个序列。
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
Zip
替代方案。B)编写一个方法来yield return
短列表的每个元素,然后继续无限期地yield return
default
。 (选项B要求您事先知道哪个列表较短。) - jpaugh.SelectMany()
方法来处理两个列表长度不同的情况:https://dev59.com/CnNA5IYBdhLWcg3wcddk#13035259 - n0099.SelectMany()
来处理两个列表的不同长度:https://stackoverflow.com/questions/958949/difference-between-select-and-selectmany/13035259#13035259 - undefinedZip
用于将两个序列合并为一个。例如,如果您有以下序列:
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",因为你可以将其中一个序列看作拉链的左侧,另一个序列视为右侧,而zip操作会将两侧元素(序列的元素)合并在一起。
它遍历两个序列并将它们的元素逐个组合成一个新的序列。因此,您取出序列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
如果你想使用命令式代码实现这个功能,你可能会像这样做:
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
});
}
不要被名称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
< p> Zip
及时出手。为了跟上我们的拉链术语,我们将把这个结果称为< code> closedZipper ,左侧拉链的项目称为< code> leftTooth ,右侧称为< code> 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
方法也会做同样的事情:一旦达到任一侧的最后一项,就会停止。 在我们的例子中,右侧的齿数(食品名称)较少,因此它将停止在 "甜甜圈"。
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>
。类似地,按钮的采样布尔值可以被解释为事件,如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
我没有足够的声望在评论区发帖,但是回答相关问题:
如果我想让zip继续处理其中一个列表到达元素结尾后怎么办?这种情况下,较短的列表元素应该使用默认值。此时输出为A1、B2、C3、D0、E0。-2015年11月19日liang 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
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
public static IEnumerable Pad(this IEnumerable input, long minLength, T value = default(T))
{
long numYielded = 0;
foreach (T element in input)
{
yield return element;
++numYielded;
}
while (numYielded < minLength)
{
yield return value;
++numYielded;
}
}
- Pagefault正如其他人所述,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语句。
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
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