อะไรคือการใช้วิธีการขยาย Enumerable.Zip ใน Linq?


131

การใช้Enumerable.Zipวิธีการขยายใน Linq คืออะไร?


2
คุณกำลังอ้างถึงสิ่งนี้: msdn.microsoft.com/en-us/library/dd267698.aspxหรือไม่ - คุณพยายามทำอะไรให้สำเร็จ?

13
มันเหมือนกับว่าซิปสองข้างมารวมกัน
Kevin Panko

คำตอบ:


192

ตัวดำเนินการ 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

42
ฉันชอบคำตอบนี้เพราะมันแสดงให้เห็นว่าเกิดอะไรขึ้นเมื่อจำนวนองค์ประกอบไม่ตรงกันคล้ายกับเอกสาร
msdn

2
จะทำอย่างไรถ้าฉันต้องการให้ zip ดำเนินการต่อโดยที่รายการหนึ่งไม่มีองค์ประกอบ ซึ่งในกรณีนี้องค์ประกอบรายการที่สั้นกว่าควรใช้ค่าเริ่มต้น เอาต์พุตในกรณีนี้จะเป็น A1, B2, C3, D0, E0
เลียง

2
@liang สองทางเลือก: A) เขียนZipทางเลือกของคุณเอง B) เขียนวิธีการ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);

เรียกว่า "zip" เนื่องจากคุณคิดว่าลำดับหนึ่งเป็นด้านซ้ายของซิปและอีกลำดับหนึ่งเป็นด้านขวาของซิปและตัวดำเนินการซิปจะดึงทั้งสองด้านเข้าด้วยกันโดยจับคู่ฟันออก ( องค์ประกอบของลำดับ) อย่างเหมาะสม


8
คำอธิบายที่ดีที่สุดที่นี่
Maxim Gershkovich

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

หรือถ้าไม่มี 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
                          });
}

ฉันชอบคำตอบนี้เช่นกันเพราะมันกล่าวถึงความคล้ายคลึงกับ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();

ในข้างต้นเรากำลังแจกแจง (การเดินทาง) ด้านซ้ายของซิปและด้านขวาของซิปและทำการผ่าตัดฟันแต่ละซี่ การผ่าตัดที่เรากำลังดำเนินการคือการต่อฟันด้านซ้าย (อักษรอาหาร) เข้ากับ:ฟันด้านขวา (ชื่ออาหาร) เราทำโดยใช้รหัสนี้:

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

ผลลัพธ์สุดท้ายคือ:

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

เกิดอะไรขึ้นกับตัวอักษร E ตัวสุดท้าย?

หากคุณกำลังแจกแจง (ดึง) ซิปเสื้อผ้าจริงและข้างหนึ่งไม่สำคัญว่าด้านซ้ายหรือด้านขวามีฟันน้อยกว่าอีกด้านหนึ่งจะเกิดอะไรขึ้น? ซิปจะหยุดตรงนั้น Zipวิธีการนี้จะทำเหมือนกันทุกประการ: จะหยุดเมื่อถึงรายการสุดท้ายในด้านใดด้านหนึ่ง ในกรณีของเราด้านขวามีฟันน้อย (ชื่ออาหาร) ดังนั้นมันจะหยุดที่ "โดนัท"


1
+1 ใช่ชื่อ "Zip" อาจสร้างความสับสนในตอนแรก บางที "Interleave" หรือ "Weave" อาจเป็นชื่อที่สื่อความหมายได้มากกว่าสำหรับวิธีนี้
BACON

1
@ เบคอนใช่ แต่ฉันก็ไม่สามารถใช้ตัวอย่างซิปของฉันได้) ฉันคิดว่าเมื่อคุณคิดว่ามันเหมือนซิปแล้วมันก็ค่อนข้างตรงไปตรงมาในภายหลัง
CodingYoshi

แม้ว่าฉันจะรู้ว่าวิธีการขยาย Zip ทำอะไรได้บ้าง แต่ฉันก็สงสัยมาตลอดว่าทำไมจึงตั้งชื่อนี้ ในศัพท์แสงทั่วไปของซอฟต์แวร์ zip มีความหมายอย่างอื่นเสมอ การเปรียบเทียบที่ยอดเยี่ยม :-) คุณต้องอ่านใจผู้สร้าง
Raghu Reddy Muttana

7

ฉันไม่มีตัวแทนที่จะโพสต์ในส่วนความคิดเห็น แต่เพื่อตอบคำถามที่เกี่ยวข้อง:

จะเกิดอะไรขึ้นถ้าฉันต้องการให้ zip ดำเนินการต่อโดยที่รายการหนึ่งไม่มีองค์ประกอบ ในกรณีนี้องค์ประกอบรายการที่สั้นกว่าควรใช้ค่าเริ่มต้น เอาต์พุตในกรณีนี้จะเป็น A1, B2, C3, D0, E0 - เลียง 19 พ.ย. 58 เวลา 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 () มีข้อแม้ : Redim Preserve ใน 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

การปรับขนาดนั้นสิ้นเปลืองโดยเฉพาะอย่างยิ่งหากคอลเล็กชันใดมีขนาดใหญ่ สิ่งที่คุณต้องการทำจริงๆคือมีการแจงนับที่ดำเนินต่อไปหลังจากสิ้นสุดคอลเลคชันโดยเติมค่าว่างตามความต้องการ (โดยไม่มีคอลเล็กชันสำรอง) คุณสามารถทำได้ด้วย: 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

จำนวนมากของคำตอบที่นี่แสดงให้เห็นแต่ไม่มีจริงๆอธิบายชีวิตจริงกรณีการใช้งานที่จะกระตุ้นการใช้งานของZipZip

รูปแบบทั่วไปโดยเฉพาะอย่างยิ่งที่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สามารถ interpretted เข้าไปในเหตุการณ์เช่น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 loop

การดำเนินการที่เคยต้องการ for loop และสองอาร์เรย์สามารถทำได้ใน foreach loop โดยใช้อ็อบเจ็กต์ที่ไม่ระบุชื่อ

ตัวอย่างที่ฉันเพิ่งค้นพบนั่นเป็นเรื่องงี่เง่า แต่อาจมีประโยชน์หากการขนานเป็นประโยชน์จะเป็นการส่งผ่านคิวบรรทัดเดียวที่มีผลข้างเคียง:

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) หมายถึงรายการถัดไปหรือ peek ในคิว วิธี Zip รวมสองสิ่งนี้ไว้ในอ็อบเจ็กต์เดียวที่ไม่ระบุชื่อโดยมีคุณสมบัติถัดไปและปัจจุบัน จากนั้นเรากรองด้วย Where และทำการเปลี่ยนแปลงด้วย AsParallel () ForAll แน่นอนว่าบิตสุดท้ายอาจเป็นเพียง foreach ปกติหรือคำสั่ง 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.