Что такое использование Перечислимого.Метод расширения Zip в Linq?


зачем Enumerable.Zip метод расширения в Linq?

8 96

8 ответов:

оператор 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

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", потому что вы думаете об одной последовательности как левая сторона молнии, а другая последовательность как правая сторона молнии и молнии оператор будет тянуть обе стороны вместе спаривание с зубами (элементы последовательности) соответствующим образом.

он перебирает две последовательности и объединяет их элементы, один за другим, в одну новую последовательность. Итак, вы берете элемент последовательности 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 бросить тебя. Это не имеет никакого отношения к сжатию, как при сжатии файла или папки (сжатие). Он фактически получает свое название от того, как работает молния на одежде: молния на одежде имеет 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

что случилось с последней буквой Е?

если вы перечисляете (вытягиваете) настоящую застежку-молнию одежды и одна сторона, не имеет значения левая сторона или правая сторона, имеет меньше зубов, чем другая сторона, что произойдет? Ну молния остановится там. Элемент Zip метод будет делать то же самое: он остановится, как только он достигнет последнего элемента с обеих сторон. В в нашем случае правая сторона имеет меньше зубов (названия продуктов питания), поэтому она остановится на "пончике".

Как уже говорилось, 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.Пропустить (1) представляет следующие или просматриваемые элементы в очереди. Метод Zip объединяет эти два объекта в один анонимный объект со следующим и текущим свойством. Затем мы фильтруем с помощью Where и вносим изменения с помощью AsParallel().Для всех. Конечно, последний бит может быть просто обычным foreach или другим оператором Select, который возвращает оскорбительные временные сегменты.

у меня нет rep points для публикации в разделе комментариев, но чтобы ответить на соответствующий вопрос:

что делать, если я хочу, чтобы zip продолжался там, где в одном списке заканчиваются элементы? В в этом случае элемент shorter list должен принимать значение по умолчанию. Выход в этом случае должны быть A1, B2, C3, D0, E0. - Лян 19 '15 ноября в 3:29

что бы вы сделали это, чтобы использовать массив.Измените размер (), чтобы заполнить более короткую последовательность со значениями по умолчанию, а затем 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

обратите внимание, что с помощью массива.Изменить размер ()есть один нюанс:Redim сохранить в 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

возвращаясь к основному ответу оригинала вопрос, еще одна интересная вещь, которую можно было бы сделать (когда длины последовательностей, которые нужно "застегнуть", разные), - это соединить их таким образом, чтобы конец из списка совпадений вместо верхней. Это может быть достигнуто путем "пропуска" соответствующего количества элементов с помощью .Скип.)(

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

выход:

C1
D2
E3

метод 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
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