Jaki jest pożytek z Enumerable.Zip
metody rozszerzenia w Linq?
Jaki jest pożytek z Enumerable.Zip
metody rozszerzenia w Linq?
Odpowiedzi:
Operator Zip scala odpowiednie elementy dwóch sekwencji przy użyciu określonej funkcji selektora.
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
alternatywę. B) napisać metodę do yield return
każdego elementu krótszej listy, a następnie kontynuować yield return
ing default
nieskończoność później. (Opcja B wymaga wcześniejszego poinformowania, która lista jest krótsza.)
Zip
służy do łączenia dwóch sekwencji w jedną. Na przykład, jeśli masz sekwencje
1, 2, 3
i
10, 20, 30
i chcesz uzyskać sekwencję będącą wynikiem mnożenia elementów w tej samej pozycji w każdej sekwencji
10, 40, 90
można powiedzieć
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
Nazywa się to „zamkiem błyskawicznym”, ponieważ myślisz o jednej sekwencji jako o lewej stronie zamka, a o drugiej jako o prawej stronie zamka błyskawicznego, a operator zamka błyskawicznego pociągnie obie strony razem, parując zęby ( elementy sekwencji) odpowiednio.
Iteruje przez dwie sekwencje i łączy ich elementy, jeden po drugim, w jedną nową sekwencję. Więc bierzesz element ciągu A, przekształcasz go odpowiednim elementem z sekwencji B, a wynik tworzy element ciągu C.
Można o tym pomyśleć, że jest podobny do tego Select
, z wyjątkiem tego, że zamiast przekształcać elementy z jednej kolekcji, działa na dwóch kolekcjach jednocześnie.
Z artykułu MSDN na temat metody :
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
Gdybyś miał to zrobić w kodzie imperatywnym, prawdopodobnie zrobiłbyś coś takiego:
for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
numbersAndWords.Add(numbers[i] + " " + words[i]);
}
Lub jeśli LINQ go nie ma Zip
, możesz to zrobić:
var numbersAndWords = numbers.Select(
(num, i) => num + " " + words[i]
);
Jest to przydatne, gdy dane są rozmieszczone na prostych listach przypominających tablice, z których każda ma tę samą długość i kolejność, a każda z nich opisuje inną właściwość tego samego zestawu obiektów. Zip
pomaga połączyć te fragmenty danych w bardziej spójną strukturę.
Więc jeśli masz tablicę nazw stanów i inną tablicę ich skrótów, możesz zebrać je w State
klasę, taką jak ta:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
NIE pozwól, aby nazwa Zip
Cię zniechęciła. Nie ma to nic wspólnego z kompresowaniem, jak z kompresowaniem pliku lub folderu (kompresja). Swoją nazwę zawdzięcza działaniu zamka błyskawicznego na ubraniu: zamek błyskawiczny na ubraniach ma 2 boki, a po każdej stronie kilka zębów. Kiedy idziesz w jednym kierunku, zamek błyskawiczny wylicza (przesuwa) obie strony i zamyka zamek, zaciskając zęby. Kiedy idziesz w innym kierunku, otwiera zęby. Kończysz otwartym lub zamkniętym zamkiem błyskawicznym.
Tak samo jest z Zip
metodą. Rozważmy przykład, w którym mamy dwie kolekcje. Jedna zawiera litery, a druga nazwę produktu spożywczego, która zaczyna się od tej litery. Dla jasności nazywam je leftSideOfZipper
i rightSideOfZipper
. Oto kod.
var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Naszym zadaniem jest wyprodukowanie jednej kolekcji, w której litera owocu oddzielona jest znakiem :
a oraz jego nazwą. Lubię to:
A : Apple
B : Banana
C : Coconut
D : Donut
Zip
na pomoc. Aby nadążyć za naszą terminologią dotyczącą zamków błyskawicznych, nazwiemy ten wynik closedZipper
i elementy lewego zamka będziemy nazywać, leftTooth
a prawą stronę nazwiemy righTooth
z oczywistych powodów:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
W powyższym wyliczamy (przesuwamy) lewą stronę zamka i prawą stronę zamka oraz wykonujemy operację na każdym zębie. Operacja, którą wykonujemy, to połączenie lewego zęba (litera pokarmu) z a, :
a następnie prawego zęba (nazwa pokarmu). Robimy to za pomocą tego kodu:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Efekt końcowy jest taki:
A : Apple
B : Banana
C : Coconut
D : Donut
Co się stało z ostatnią literą E?
Jeśli wyliczasz (ciągniesz) prawdziwy zamek do ubrania i jedną stronę, nieważne, czy lewa strona, czy prawa strona, ma mniej zębów niż druga strona, co się stanie? Zamek błyskawiczny się tam zatrzyma. Zip
Metoda zrobi dokładnie to samo: Będzie zatrzymać po osiągnięciu ostatniego elementu po obu stronach. W naszym przypadku prawa strona ma mniej zębów (nazw potraw), więc zatrzyma się na „Donut”.
Wiele odpowiedzi tutaj pokazuje Zip
, ale tak naprawdę bez wyjaśnienia rzeczywistego przypadku użycia, który motywowałby użycie Zip
.
Jeden szczególnie powszechny wzorzec, który Zip
jest fantastyczny do iterowania po kolejnych parach rzeczy. Odbywa się to przez iteracji jest przeliczalny X
z siebie, omijając 1 elementu: x.Zip(x.Skip(1)
. Przykład wizualny:
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)
Te kolejne pary są przydatne do znajdowania pierwszych różnic między wartościami. Na przykład IEnumable<MouseXPosition>
do produkcji można użyć kolejnych par IEnumerable<MouseXDelta>
. Podobnie próbkowane bool
wartości a button
mogą być interpretowane jako zdarzenia takie jak NotPressed
/ Clicked
/ Held
/ Released
. Te zdarzenia mogą następnie kierować wywołania do metod delegowania. Oto przykład:
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) );
}
}
Wydruki:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Nie mam punktów przedstawiciela do opublikowania w sekcji komentarzy, ale aby odpowiedzieć na powiązane pytanie:
Co jeśli chcę, aby zip był kontynuowany, gdy na jednej liście zabrakło elementów? W takim przypadku krótszy element listy powinien przyjąć wartość domyślną. Wyjście w tym przypadku to A1, B2, C3, D0, E0. - liang listopad 19 '15 o 3:29
To, co byś zrobił, to użycie Array.Resize () do wypełnienia krótszej sekwencji wartościami domyślnymi, a następnie Zip () je razem.
Przykład kodu:
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);
Wynik:
A1
B2
C3
D0
E0
Należy pamiętać, że użycie Array.Resize () ma zastrzeżenie : Redim Preserve in C #?
Jeśli nie wiadomo, która sekwencja będzie krótsza, można utworzyć funkcję, która ją ogranicza:
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());
}
Wyjście zwykłego .Zip () wraz z ZipDefault ():
A1 A1
B2 B2
C3 C3
D0
E0
Wracając do głównej odpowiedzi na pierwotne pytanie , kolejną interesującą rzeczą, którą można by chcieć zrobić (gdy długości sekwencji do „spakowania” są różne) jest połączenie ich w taki sposób, aby koniec listy mecze zamiast góry. Można to osiągnąć "pomijając" odpowiednią liczbę elementów za pomocą .Skip ().
foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);
Wynik:
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; } }
Jak powiedzieli inni, Zip pozwala łączyć dwie kolekcje do wykorzystania w dalszych instrukcjach Linq lub w pętli foreach.
Operacje, które kiedyś wymagały pętli for i dwóch tablic, można teraz wykonać w pętli foreach przy użyciu anonimowego obiektu.
Przykład, który właśnie odkryłem, jest trochę głupi, ale mógłby być przydatny, gdyby równoległość była korzystna, to przechodzenie kolejki pojedynczej linii z efektami ubocznymi:
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 reprezentuje bieżące lub usunięte z kolejki elementy w kolejce (ostatni element jest obcięty przez Zip). timeSegments.Skip (1) reprezentuje następne lub podglądane elementy w kolejce. Metoda Zip łączy te dwa w jeden anonimowy obiekt z właściwościami Next i Current. Następnie filtrujemy za pomocą Where i wprowadzamy zmiany za pomocą AsParallel (). ForAll. Oczywiście ostatni bit może być zwykłą instrukcją foreach lub inną instrukcją Select, która zwraca niewłaściwe segmenty czasu.
Metoda Zip pozwala na „scalenie” dwóch niepowiązanych ze sobą sekwencji przy użyciu dostawcy funkcji scalającej przez Ciebie, osobę dzwoniącą. Przykład w witrynie MSDN jest całkiem dobry w zademonstrowaniu, co można zrobić za pomocą Zipa. W tym przykładzie bierzesz dwie dowolne, niepowiązane sekwencje i łączysz je za pomocą dowolnej funkcji (w tym przypadku po prostu łączysz elementy z obu sekwencji w jeden ciąg).
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