Việc sử dụng Enumerable.Zip
phương pháp mở rộng trong Linq là gì?
Việc sử dụng Enumerable.Zip
phương pháp mở rộng trong Linq là gì?
Câu trả lời:
Toán tử Zip hợp nhất các phần tử tương ứng của hai chuỗi bằng cách sử dụng chức năng chọn được chỉ định.
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
thay thế của riêng bạn . B) Viết một phương thức cho yield return
từng phần tử của danh sách ngắn hơn, và sau đó tiếp tục yield return
ing default
vô thời hạn sau đó. (Tùy chọn B yêu cầu bạn biết trước danh sách nào ngắn hơn.)
Zip
là để kết hợp hai chuỗi thành một. Ví dụ: nếu bạn có trình tự
1, 2, 3
và
10, 20, 30
và bạn muốn chuỗi đó là kết quả của việc nhân các phần tử ở cùng một vị trí trong mỗi chuỗi để có được
10, 40, 90
bạn có thể nói
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
Nó được gọi là "zip" bởi vì bạn nghĩ về một chuỗi là phía bên trái của khóa kéo, và chuỗi khác là phía bên phải của khóa kéo, và toán tử zip sẽ kéo hai bên khớp nối với nhau ( các yếu tố của chuỗi) một cách thích hợp.
Nó lặp qua hai chuỗi và kết hợp các yếu tố của chúng, từng cái một, thành một chuỗi mới duy nhất. Vì vậy, bạn lấy một phần tử của chuỗi A, biến đổi nó với phần tử tương ứng từ chuỗi B và kết quả tạo thành một phần tử của chuỗi C.
Một cách để nghĩ về nó là nó tương tự Select
, ngoại trừ thay vì chuyển đổi các mục từ một bộ sưu tập, nó hoạt động trên hai bộ sưu tập cùng một lúc.
Từ bài viết MSDN về phương pháp :
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
Nếu bạn đã làm điều này trong mã bắt buộc, có lẽ bạn sẽ làm một cái gì đó như thế này:
for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
numbersAndWords.Add(numbers[i] + " " + words[i]);
}
Hoặc nếu LINQ không có Zip
trong đó, bạn có thể làm điều này:
var numbersAndWords = numbers.Select(
(num, i) => num + " " + words[i]
);
Điều này hữu ích khi bạn có dữ liệu trải thành các danh sách đơn giản, giống như mảng, mỗi danh sách có cùng độ dài và thứ tự và mỗi mô tả một thuộc tính khác nhau của cùng một bộ đối tượng. Zip
giúp bạn đan những mẩu dữ liệu đó lại với nhau thành một cấu trúc mạch lạc hơn.
Vì vậy, nếu bạn có một mảng tên trạng thái và một mảng chữ viết tắt khác, bạn có thể đối chiếu chúng thành một State
lớp như vậy:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
ĐỪNG để tên Zip
ném bạn đi. Nó không có gì để làm với việc nén như khi nén một tập tin hoặc một thư mục (nén). Nó thực sự có được tên của nó từ cách dây kéo trên quần áo hoạt động: Khóa kéo trên quần áo có 2 mặt và mỗi bên có một chùm răng. Khi bạn đi theo một hướng, dây kéo liệt kê (di chuyển) cả hai bên và đóng dây kéo bằng cách nghiến răng. Khi bạn đi theo hướng khác, nó sẽ mở răng. Bạn có thể kết thúc với một dây kéo mở hoặc đóng.
Đó là cùng một ý tưởng với Zip
phương pháp. Hãy xem xét một ví dụ nơi chúng tôi có hai bộ sưu tập. Một người giữ các chữ cái và người kia giữ tên của một mặt hàng thực phẩm bắt đầu bằng chữ cái đó. Đối với mục đích rõ ràng tôi đang gọi họ leftSideOfZipper
và rightSideOfZipper
. Đây là mã.
var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Nhiệm vụ của chúng tôi là sản xuất một bộ sưu tập có chữ cái quả được phân tách bằng chữ a :
và tên của nó. Như thế này:
A : Apple
B : Banana
C : Coconut
D : Donut
Zip
để giải cứu. Để theo kịp thuật ngữ dây kéo của chúng tôi, chúng tôi sẽ gọi kết quả này closedZipper
và các mặt hàng của dây kéo bên trái chúng tôi sẽ gọi leftTooth
và bên phải chúng tôi sẽ gọi righTooth
vì lý do rõ ràng:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
Ở trên, chúng tôi đang liệt kê (đi du lịch) bên trái của dây kéo và bên phải của dây kéo và thực hiện một thao tác trên mỗi răng. Các hoạt động chúng tôi đang thực hiện là nối răng trái (chữ cái thực phẩm) với một :
và sau đó là răng phải (tên thực phẩm). Chúng tôi làm điều đó bằng cách sử dụng mã này:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Kết quả cuối cùng là đây:
A : Apple
B : Banana
C : Coconut
D : Donut
Điều gì đã xảy ra với chữ E cuối cùng?
Nếu bạn đang liệt kê (kéo) một dây kéo quần áo thật và một bên, không quan trọng bên trái hay bên phải, có ít răng hơn bên kia, chuyện gì sẽ xảy ra? Vâng dây kéo sẽ dừng ở đó. Các Zip
phương pháp sẽ làm giống hệt nhau: Nó sẽ dừng khi nó đã đạt đến mục cuối cùng ở hai bên. Trong trường hợp của chúng tôi, bên phải có ít răng (tên thực phẩm) nên nó sẽ dừng lại ở "Donut".
Tôi không có điểm đại diện để đăng trong phần bình luận, nhưng để trả lời câu hỏi liên quan:
Điều gì xảy ra nếu tôi muốn zip tiếp tục khi một danh sách hết các phần tử? Trong trường hợp đó, phần tử danh sách ngắn hơn sẽ lấy giá trị mặc định. Đầu ra trong trường hợp này là A1, B2, C3, D0, E0. - liang 19/11/2015 lúc 3:29
Những gì bạn sẽ làm là sử dụng Array.Resize () để loại bỏ chuỗi ngắn hơn với các giá trị mặc định và sau đó Zip () chúng lại với nhau.
Mã ví dụ:
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);
Đầu ra:
A1
B2
C3
D0
E0
Xin lưu ý rằng việc sử dụng Array.Resize () có một cảnh báo : Redim Preserve trong C #?
Nếu không biết chuỗi nào sẽ là chuỗi ngắn hơn, một hàm có thể được tạo ra để đánh bại nó:
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());
}
Đầu ra của .Zip () đơn giản cùng với ZipDefault ():
A1 A1
B2 B2
C3 C3
D0
E0
Quay trở lại câu trả lời chính của câu hỏi ban đầu , một điều thú vị khác mà người ta có thể muốn làm (khi độ dài của các chuỗi được "nén" là khác nhau) là tham gia chúng theo cách sao cho kết thúc danh sách phù hợp thay vì hàng đầu. Điều này có thể được thực hiện bằng cách "bỏ qua" số lượng mục thích hợp bằng cách sử dụng .Skip ().
foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);
Đầu ra:
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; } }
Rất nhiều câu trả lời ở đây chứng minh Zip
, nhưng không thực sự giải thích một trường hợp sử dụng thực tế nào sẽ thúc đẩy việc sử dụng Zip
.
Một mô hình đặc biệt phổ biến Zip
là tuyệt vời để lặp đi lặp lại trên các cặp điều tiếp theo. Điều này được thực hiện bằng cách lặp lại một liệt kê X
với chính nó, bỏ qua 1 phần tử : x.Zip(x.Skip(1)
. Ví dụ trực quan:
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)
Các cặp liên tiếp này rất hữu ích cho việc tìm kiếm sự khác biệt đầu tiên giữa các giá trị. Ví dụ, các cặp liên tiếp IEnumable<MouseXPosition>
có thể được sử dụng để sản xuất IEnumerable<MouseXDelta>
. Tương tự, bool
các giá trị được lấy mẫu của a button
có thể được diễn giải thành các sự kiện như NotPressed
/ Clicked
/ Held
/ Released
. Những sự kiện sau đó có thể thúc đẩy các cuộc gọi đến các phương thức ủy nhiệm. Đây là một ví dụ:
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) );
}
}
Bản in:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Như những người khác đã nêu, Zip cho phép bạn kết hợp hai bộ sưu tập để sử dụng trong các câu lệnh Linq tiếp theo hoặc một vòng lặp foreach.
Các hoạt động được sử dụng để yêu cầu một vòng lặp for và hai mảng hiện có thể được thực hiện trong một vòng lặp foreach bằng cách sử dụng một đối tượng ẩn danh.
Một ví dụ tôi vừa phát hiện ra, đó là một điều ngớ ngẩn, nhưng có thể hữu ích nếu việc song song hóa có lợi sẽ là một đường truyền hàng đợi duy nhất với các tác dụng phụ:
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 đại diện cho các mục hiện tại hoặc bị mất trong hàng đợi (phần tử cuối cùng được cắt bớt bởi Zip). timeSegments.Skip (1) đại diện cho các mục tiếp theo hoặc nhìn trộm trong hàng đợi. Phương thức Zip kết hợp cả hai thành một đối tượng ẩn danh duy nhất với thuộc tính Tiếp theo và Hiện tại. Sau đó, chúng tôi lọc với Where và thực hiện các thay đổi với AsParallel (). For ALL. Tất nhiên, bit cuối cùng chỉ có thể là một thông báo trước thông thường hoặc một câu lệnh Chọn khác trả về các phân đoạn thời gian vi phạm.
Phương thức Zip cho phép bạn "hợp nhất" hai chuỗi không liên quan, sử dụng nhà cung cấp chức năng hợp nhất bởi bạn, người gọi. Ví dụ trên MSDN thực sự khá tốt trong việc chứng minh những gì bạn có thể làm với Zip. Trong ví dụ này, bạn lấy hai chuỗi tùy ý, không liên quan và kết hợp chúng bằng cách sử dụng một hàm tùy ý (trong trường hợp này, chỉ ghép các mục từ cả hai chuỗi thành một chuỗi).
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