Каково использование метода расширения 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