Apa gunanya Enumerable.Zip
metode ekstensi di Linq?
Apa gunanya Enumerable.Zip
metode ekstensi di Linq?
Jawaban:
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
Zip
alternatif Anda sendiri . B) Write metode untuk yield return
setiap elemen dari daftar pendek, dan kemudian melanjutkan yield return
ing default
tanpa batas setelahnya. (Opsi B mengharuskan Anda untuk mengetahui terlebih dahulu daftar mana yang lebih pendek.)
Zip
adalah 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.
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 Zip
di 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. Zip
membantu 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 State
kelas seperti:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
JANGAN biarkan nama itu Zip
mengusir 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 Zip
metodenya. 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 leftSideOfZipper
dan 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
Zip
untuk menyelamatkan. Untuk mengikuti terminologi ritsleting kami, kami akan memanggil hasil ini closedZipper
dan item dari ritsleting kiri akan kami panggil leftTooth
dan sisi kanan kami akan memanggil righTooth
untuk 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 Zip
Metode 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".
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
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; } }
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 Zip
fantastis untuk mengulangi hal-hal yang berurutan. Hal ini dilakukan dengan iterasi sebuah enumerable X
dengan 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, bool
nilai sampel dari a button
dapat 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
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.
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
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