Для чого використовується метод розширення Enumerable.Zip в Linq?


130

Для чого використовується Enumerable.Zipметод розширення в Linq?


2
Ви посилаєтесь на це: msdn.microsoft.com/en-us/library/dd267698.aspx ? - Що ви намагаєтеся досягти?

13
Це як дві сторони блискавки, що поєднуються.
Кевін Панько

Відповіді:


191

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

41
Мені подобається ця відповідь, оскільки вона показує, що відбувається, коли кількість елементів не збігається, подібно до документації
msdn

2
що робити, якщо я хочу, щоб zip продовжувався там, де в одному списку не вистачає елементів? в цьому випадку коротший елемент списку повинен приймати значення за замовчуванням. Вихід у цьому випадку буде A1, B2, C3, D0, E0.
laang

2
@liang Два варіанти: А) Напишіть власну Zipальтернативу. Б) Написати метод yield returnкожному елементу короткий список, а потім продовжити yield returnІНГ на defaultневизначений термін після цього. (Варіант B вимагає, щоб ви заздалегідь дізналися, який список коротший.)
jpaugh

105

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);

Це називається "блискавка", тому що ви вважаєте одну послідовність як ліву частину блискавки, а іншу послідовність як праву частину блискавки, а оператор поштового зв’язку витягне дві сторони разом, з'єднуючи зуби ( елементи послідовності) відповідним чином.


8
Однозначно найкраще пояснення тут.
Максим Гершкович

2
Полюбив приклад блискавки. Це було так природно. Моє первісне враження було, якщо це має щось спільне зі швидкістю чи щось подібне, як ніби ви перебираєте вулицю на своєму автомобілі.
RBT

23

Він повторюється через дві послідовності і поєднує їх елементи, по черзі, в одну нову послідовність. Отже, ви берете елемент послідовності 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]);
}

Або якщо 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
iliketocode

17

НЕ дозволяйте, щоб ім’я 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Метод буде робити точно так же: Він зупиниться , як тільки він досяг останнього пункту з обох сторін. У нашому випадку права сторона має менше зубів (назв їжі), тому вона зупиниться на «Пончику».


1
+1. Так, назва "Zip" може спочатку заплутатись. Можливо, "Interleave" або "Weave" були б більш описовими назвами методу.
БАКОН

1
@bacon так, але тоді я б не зміг використати свій приклад на блискавці;) Я думаю, що коли ти розбереш його як блискавку, це буде досить прямо вперед.
CodingYoshi

Хоча я точно знав, що робить метод розширення Zip, мені завжди було цікаво, чому його так назвали. У загальному жаргоні програмного забезпечення zip завжди означало щось інше. Чудова аналогія :-) Ви, мабуть, читали розум творця.
Рагу Редді Муттана

7

У мене немає балів для повторної публікації в розділі коментарів, але щоб відповісти на відповідне запитання:

Що робити, якщо я хочу, щоб 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; } }
Pagefault

Здається, я не впевнений, як вдало відформатувати код у коментарі ...
Pagefault

7

Багато відповідей тут демонструють 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

6

Як зазначають інші, 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, який повертає оскорблені часові сегменти.


3

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

0
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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.