Что такое использование Перечислимого.Метод расширения Zip в Linq?
зачем Enumerable.Zip
метод расширения в Linq?
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
, за исключением того, что вместо преобразования элементов из одной коллекции он работает сразу с двумя коллекциями.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