Каково использование Enumerable.Zip
метода расширения в Linq?
Каково использование метода расширения Enumerable.Zip в Linq?
Ответ 1
Оператор 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);
Ouput
A1
B2
C3
Ответ 2
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 потянет обе стороны вместе, соединяя зубы (элементы последовательности) соответственно.
Ответ 3
Он выполняет итерацию через две последовательности и объединяет их элементы один за другим в одну новую последовательность. Таким образом, вы берете элемент последовательности 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
});
}
Ответ 4
Как указывали другие, 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, который возвращает сегменты времени оскорбления.
Ответ 5
НЕ разрешайте вызывать имя Zip
. Это не имеет ничего общего с застежкой-молнией, как при архивировании файла или папки (сжатии). Это на самом деле получает свое название от того, как молния на одежде работает: молния на одежде имеет 2 стороны, и каждая сторона имеет кучу зубов. Когда вы идете в одном направлении, молния перечисляет (перемещает) обе стороны и закрывает молнию, сжимая зубы. Когда вы идете в другом направлении, он открывает зубы. Вы либо заканчиваете открытой, либо закрытой молнией.
Это та же идея с методом 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
будет делать то же самое: он остановится, как только он достигнет последнего элемента с обеих сторон. В нашем случае правая сторона имеет меньше зубов (названия продуктов питания), поэтому она останавливается у "пончика".
Ответ 6
Метод 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
У меня нет комментариев для публикации в разделе комментариев, но для ответа на соответствующий вопрос:
Что делать, если я хочу, чтобы zip продолжался, когда один список заканчивается из элементов? В в этом случае более короткий элемент списка должен принимать значение по умолчанию. Вывод в этом случае - A1, B2, C3, D0, E0. - liang ноя 19 '15 в 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 на С#?
Если неизвестно, какая последовательность будет более короткой, можно создать функцию, которая его оспаривает:
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
Ответ 8
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