Для чого використовується 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
альтернативу. Б) Написати метод yield return
кожному елементу короткий список, а потім продовжити yield return
ІНГ на default
невизначений термін після цього. (Варіант B вимагає, щоб ви заздалегідь дізналися, який список коротший.)
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);
Це називається "блискавка", тому що ви вважаєте одну послідовність як ліву частину блискавки, а іншу послідовність як праву частину блискавки, а оператор поштового зв’язку витягне дві сторони разом, з'єднуючи зуби ( елементи послідовності) відповідним чином.
Він повторюється через дві послідовності і поєднує їх елементи, по черзі, в одну нову послідовність. Отже, ви берете елемент послідовності 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]);
}
Або якщо Zip
в ньому не було LINQ , ви можете зробити це:
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
});
}
Select
НЕ дозволяйте, щоб ім’я 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();
У вищесказаному ми перераховуємо (мандруємо) лівою стороною блискавки та правою стороною блискавки та виконуємо операцію на кожному зубі. Операція, яку ми виконуємо, - це поєднання лівого зуба (харчова літера) з a, :
а потім правого зуба (назва їжі). Ми робимо це за допомогою цього коду:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Кінцевий результат такий:
A : Apple
B : Banana
C : Coconut
D : Donut
Що сталося з останньою буквою Е?
Якщо ви перераховуєте справжню застібку-блискавку для одягу та одну сторону, неважливо, ні ліва, ні права, має менше зубів, ніж інша сторона, що буде? Ну, блискавка там зупиниться. Zip
Метод буде робити точно так же: Він зупиниться , як тільки він досяг останнього пункту з обох сторін. У нашому випадку права сторона має менше зубів (назв їжі), тому вона зупиниться на «Пончику».
У мене немає балів для повторної публікації в розділі коментарів, але щоб відповісти на відповідне запитання:
Що робити, якщо я хочу, щоб zip продовжувався там, де в одному списку не вистачає елементів? У цьому випадку коротший елемент списку повинен приймати значення за замовчуванням. Вихід у цьому випадку буде A1, B2, C3, D0, E0. - laang 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 () має застереження : Редім-резерв у 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
Повернувшись до основної відповіді на початкове запитання , ще одна цікава річ, яку можна було б зробити (коли довжини послідовностей, які слід "блискавки" різняться), - це приєднати їх таким чином, щоб кінець списку сірники замість верху. Це можна досягти, "пропустивши" відповідну кількість елементів за допомогою .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<T> Pad<T>(this IEnumerable<T> 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; } }
Багато відповідей тут демонструють Zip
, але не пояснюючи справжнього випадку використання, який мотивував би використання Zip
.
Один особливо поширений зразок, який Zip
є фантастичним для повторення кількох речей. Це робиться ітеріруя Перечіслімий X
з собою, пропустивши 1 елемент: x.Zip(x.Skip(1)
. Візуальний приклад:
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>
. Точно так же, вибіркові bool
значення а button
може бути в події інтерпретуються з як 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 дозволяє комбінувати дві колекції для використання в подальших операторах Linq або циклі foreach.
Операції, які раніше вимагали циклу та двох масивів, тепер можна виконувати в циклі 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. Потім ми фільтруємо з Де і вносимо зміни з AsParallel (). ForAll. Звичайно, останній біт може бути просто звичайним передбаченням або іншим оператором Select, який повертає оскорблені часові сегменти.
Метод 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