Apa gunanya metode ekstensi Enumerable.Zip di Linq?


Jawaban:


191

Operator Zip menggabungkan elemen yang sesuai dari dua urutan menggunakan fungsi pemilih yang ditentukan.

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

41
Saya suka jawaban ini karena ini menunjukkan apa yang terjadi ketika jumlah elemen tidak cocok, mirip dengan dokumentasi
msdn

2
bagaimana jika saya ingin zip melanjutkan di mana satu daftar kehabisan elemen? dalam hal ini elemen daftar pendek harus mengambil nilai default. Output dalam hal ini adalah A1, B2, C3, D0, E0.
liang

2
@liang Dua pilihan: A) Tuliskan Zipalternatif Anda sendiri . B) Write metode untuk yield returnsetiap elemen dari daftar pendek, dan kemudian melanjutkan yield returning defaulttanpa batas setelahnya. (Opsi B mengharuskan Anda untuk mengetahui terlebih dahulu daftar mana yang lebih pendek.)
jpaugh

105

Zipadalah untuk menggabungkan dua urutan menjadi satu. Misalnya, jika Anda memiliki urutan

1, 2, 3

dan

10, 20, 30

dan Anda ingin urutan yang merupakan hasil dari mengalikan elemen di posisi yang sama di setiap urutan untuk memperoleh

10, 40, 90

Anda bisa mengatakan

var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);

Ini disebut "zip" karena Anda menganggap satu urutan sebagai sisi kiri ritsleting, dan urutan lainnya sebagai sisi kanan ritsleting, dan operator zip akan menarik kedua sisi bersama-sama berpasangan dari gigi ( elemen urutan) dengan tepat.


8
Pasti penjelasan terbaik di sini.
Maxim Gershkovich

2
Mencintai contoh ritsleting. Itu sangat alami. Kesan awal saya adalah apakah itu ada hubungannya dengan kecepatan atau sesuatu seperti itu seolah-olah Anda melewati jalanan di mobil Anda.
RBT

23

Iterates melalui dua urutan dan menggabungkan elemen-elemen mereka, satu per satu, menjadi satu urutan baru. Jadi Anda mengambil elemen urutan A, mentransformasikannya dengan elemen yang sesuai dari urutan B, dan hasilnya membentuk elemen urutan C.

Salah satu cara untuk memikirkannya adalah bahwa itu mirip dengan Select, kecuali alih-alih mengubah item dari satu koleksi, itu berfungsi pada dua koleksi sekaligus.

Dari artikel MSDN pada metode :

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

Jika Anda melakukan ini dalam kode imperatif, Anda mungkin akan melakukan sesuatu seperti ini:

for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
    numbersAndWords.Add(numbers[i] + " " + words[i]);
}

Atau jika LINQ tidak ada Zipdi dalamnya, Anda bisa melakukan ini:

var numbersAndWords = numbers.Select(
                          (num, i) => num + " " + words[i]
                      );

Ini berguna ketika Anda memiliki data yang menyebar ke daftar sederhana, seperti array, masing-masing dengan panjang dan urutan yang sama, dan masing-masing menggambarkan properti berbeda dari set objek yang sama. Zipmembantu Anda menyatukan potongan-potongan data menjadi struktur yang lebih koheren.

Jadi jika Anda memiliki larik nama negara dan larik singkatan lainnya, Anda bisa menyusunnya menjadi Statekelas seperti:

IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
    return stateNames.Zip(statePopulations, 
                          (name, population) => new State()
                          {
                              Name = name,
                              Population = population
                          });
}

Saya juga menyukai jawaban ini, karena menyebutkan kemiripan denganSelect
iliketocode

17

JANGAN biarkan nama itu Zipmengusir Anda. Ini tidak ada hubungannya dengan zip seperti dalam zip file atau folder (mengompresi). Ini sebenarnya mendapatkan namanya dari bagaimana ritsleting pada pakaian bekerja: Ritsleting pada pakaian memiliki 2 sisi dan setiap sisi memiliki banyak gigi. Ketika Anda pergi dalam satu arah, ritsleting menyebutkan (perjalanan) kedua sisi dan menutup ritsleting dengan mengepalkan gigi. Ketika Anda pergi ke arah lain itu membuka gigi. Anda bisa mengakhiri dengan ritsleting terbuka atau tertutup.

Itu adalah ide yang sama dengan Zipmetodenya. Perhatikan contoh di mana kami memiliki dua koleksi. Satu memegang surat dan yang lainnya memegang nama item makanan yang dimulai dengan surat itu. Untuk tujuan kejelasan saya memanggil mereka leftSideOfZipperdan rightSideOfZipper. Ini kodenya.

var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };

Tugas kita adalah menghasilkan satu koleksi yang memiliki huruf buah yang dipisahkan oleh :dan namanya. Seperti ini:

A : Apple
B : Banana
C : Coconut
D : Donut

Zipuntuk menyelamatkan. Untuk mengikuti terminologi ritsleting kami, kami akan memanggil hasil ini closedZipperdan item dari ritsleting kiri akan kami panggil leftToothdan sisi kanan kami akan memanggil righToothuntuk alasan yang jelas:

var closedZipper = leftSideOfZipper
   .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();

Di atas kita menghitung (bepergian) sisi kiri ritsleting dan sisi kanan ritsleting dan melakukan operasi pada setiap gigi. Operasi yang kami lakukan adalah menggabungkan gigi kiri (surat makanan) dengan :dan kemudian gigi kanan (nama makanan). Kami melakukannya dengan menggunakan kode ini:

(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)

Hasil akhirnya adalah ini:

A : Apple
B : Banana
C : Coconut
D : Donut

Apa yang terjadi pada huruf E terakhir?

Jika Anda menghitung (menarik) ritsleting pakaian asli dan satu sisi, tidak masalah sisi kiri atau sisi kanan, memiliki lebih sedikit gigi daripada sisi lainnya, apa yang akan terjadi? Ritsleting akan berhenti di situ. The ZipMetode akan melakukan persis sama: Ini akan berhenti setelah mencapai item terakhir di kedua sisi. Dalam kasus kami sisi kanan memiliki lebih sedikit gigi (nama makanan) sehingga akan berhenti di "Donat".


1
+1. Ya, nama "Zip" bisa membingungkan pada awalnya. Mungkin "Interleave" atau "Weave" akan menjadi nama yang lebih deskriptif untuk metode ini.
BACON

1
@ daging ya tapi kemudian saya tidak akan bisa menggunakan contoh ritsleting saya;) Saya pikir begitu Anda tahu itu seperti ritsleting, itu cukup lurus ke depan setelah itu.
CodingYoshi

Meskipun saya tahu persis apa metode ekstensi Zip tidak, saya selalu ingin tahu mengapa itu dinamai begitu. Dalam jargon umum perangkat lunak zip selalu berarti sesuatu yang lain. Analogi yang bagus :-) Anda pasti sudah membaca pikiran pencipta.
Raghu Reddy Muttana

7

Saya tidak memiliki poin perwakilan untuk dikirim di bagian komentar, tetapi untuk menjawab pertanyaan terkait:

Bagaimana jika saya ingin zip melanjutkan di mana satu daftar kehabisan elemen? Dalam hal ini elemen daftar pendek harus mengambil nilai default. Output dalam hal ini adalah A1, B2, C3, D0, E0. - liang 19 Nov '15 jam 3:29

Apa yang akan Anda lakukan adalah menggunakan Array.Resize () untuk pad-out urutan yang lebih pendek dengan nilai default, dan kemudian Zip () bersama-sama.

Contoh kode:

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

Keluaran:

A1
B2
C3
D0
E0

Harap dicatat bahwa menggunakan Array.Resize () memiliki peringatan : Redim Preserve dalam C #?

Jika tidak diketahui urutan mana yang akan menjadi lebih pendek, sebuah fungsi dapat dibuat yang mengikutinya:

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

Output dari .Zip () bersama ZipDefault ():

A1 A1
B2 B2
C3 C3
   D0
   E0

Kembali ke jawaban utama dari pertanyaan awal , hal menarik lain yang mungkin ingin dilakukan seseorang (ketika panjang urutan "zip" berbeda) adalah bergabung dengan mereka sedemikian rupa sehingga akhir daftar cocok bukan bagian atas. Ini dapat dilakukan dengan "melompati" jumlah item yang sesuai menggunakan .Skip ().

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

Keluaran:

C1
D2
E3

Ubah ukuran adalah sia-sia, terutama jika salah satu koleksi besar. Apa yang benar-benar ingin Anda lakukan adalah memiliki penghitungan yang berlanjut setelah akhir koleksi, mengisinya dengan nilai kosong sesuai permintaan (tanpa koleksi dukungan). Anda dapat melakukannya dengan: 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

Sepertinya saya tidak yakin bagaimana cara memformat kode dalam komentar ...
Pagefault

7

Banyak jawaban di sini menunjukkan Zip, tetapi tanpa benar-benar menjelaskan kasus penggunaan kehidupan nyata yang akan memotivasi penggunaan Zip.

Salah satu pola umum yang Zipfantastis untuk mengulangi hal-hal yang berurutan. Hal ini dilakukan dengan iterasi sebuah enumerable Xdengan dirinya sendiri, melompat-lompat 1 elemen: x.Zip(x.Skip(1). Contoh Visual:

 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)

Pasangan berturut-turut ini berguna untuk menemukan perbedaan pertama antara nilai. Misalnya, pasangan berturut-turut IEnumable<MouseXPosition>dapat digunakan untuk menghasilkan IEnumerable<MouseXDelta>. Demikian pula, boolnilai sampel dari a buttondapat diinterpretasikan ke dalam peristiwa seperti NotPressed/ Clicked/ Held/ Released. Acara-acara tersebut kemudian dapat mendorong panggilan untuk mendelegasikan metode. Ini sebuah contoh:

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

Cetakan:

NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked

6

Seperti yang telah dinyatakan orang lain, Zip memungkinkan Anda menggabungkan dua koleksi untuk digunakan dalam pernyataan Linq lebih lanjut atau loop foreach.

Operasi yang dulu memerlukan for for loop dan dua array sekarang dapat dilakukan dalam foreach loop menggunakan objek anonim.

Contoh yang baru saja saya temukan, itu agak konyol, tetapi bisa berguna jika paralelisasi bermanfaat akan menjadi satu baris Antrian traversal dengan efek samping:

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 merepresentasikan item saat ini atau dequeued dalam antrian (elemen terakhir dipotong oleh Zip). timeSegments.Skip (1) mewakili item berikutnya atau mengintip dalam antrian. Metode Zip menggabungkan keduanya menjadi objek anonim tunggal dengan properti Berikutnya dan Sekarang. Lalu kami memfilter dengan Di mana dan melakukan perubahan dengan AsParallel (). ForAll. Tentu saja bit terakhir hanya bisa menjadi foreach biasa atau pernyataan Select lain yang mengembalikan segmen waktu yang menyinggung.


3

Metode Zip memungkinkan Anda untuk "menggabungkan" dua urutan yang tidak terkait, menggunakan penyedia fungsi penggabungan oleh Anda, pemanggil. Contoh pada MSDN sebenarnya cukup bagus untuk menunjukkan apa yang dapat Anda lakukan dengan Zip. Dalam contoh ini, Anda mengambil dua urutan yang arbitrer dan tidak terkait, dan menggabungkannya menggunakan fungsi arbitrer (dalam hal ini, hanya menggabungkan item dari kedua urutan menjadi string tunggal).

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.