การใช้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);
ouput
A1
B2
C3
Zip
ทางเลือกของคุณเอง B) เขียนวิธีการ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);
เรียกว่า "zip" เนื่องจากคุณคิดว่าลำดับหนึ่งเป็นด้านซ้ายของซิปและอีกลำดับหนึ่งเป็นด้านขวาของซิปและตัวดำเนินการซิปจะดึงทั้งสองด้านเข้าด้วยกันโดยจับคู่ฟันออก ( องค์ประกอบของลำดับ) อย่างเหมาะสม
มันวนซ้ำผ่านสองลำดับและรวมองค์ประกอบของพวกเขาทีละรายการเป็นลำดับใหม่เดียว ดังนั้นคุณจึงนำองค์ประกอบของลำดับ 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
อย่าปล่อยให้ชื่อนี้ทำให้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
วิธีการนี้จะทำเหมือนกันทุกประการ: จะหยุดเมื่อถึงรายการสุดท้ายในด้านใดด้านหนึ่ง ในกรณีของเราด้านขวามีฟันน้อย (ชื่ออาหาร) ดังนั้นมันจะหยุดที่ "โดนัท"
ฉันไม่มีตัวแทนที่จะโพสต์ในส่วนความคิดเห็น แต่เพื่อตอบคำถามที่เกี่ยวข้อง:
จะเกิดอะไรขึ้นถ้าฉันต้องการให้ 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; } }
จำนวนมากของคำตอบที่นี่แสดงให้เห็นแต่ไม่มีจริงๆอธิบายชีวิตจริงกรณีการใช้งานที่จะกระตุ้นการใช้งานของ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
สามารถ 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
ตามที่คนอื่น ๆ ระบุไว้ 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 อื่นที่ส่งคืนส่วนเวลาที่กระทำผิด
วิธีการ 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