Qual è l'uso del Enumerable.Zip
metodo di estensione in Linq?
Qual è l'uso del Enumerable.Zip
metodo di estensione in Linq?
Risposte:
L'operatore Zip unisce gli elementi corrispondenti di due sequenze utilizzando una funzione di selezione specificata.
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
alternativa. B) Scrivi un metodo per yield return
ogni elemento della lista più corta, e poi continuare yield return
ing default
tempo indeterminato in seguito. (L'opzione B richiede di sapere in anticipo quale elenco è più breve.)
Zip
serve per combinare due sequenze in una. Ad esempio, se hai le sequenze
1, 2, 3
e
10, 20, 30
e si desidera ottenere la sequenza risultante dalla moltiplicazione di elementi nella stessa posizione in ciascuna sequenza
10, 40, 90
potresti dire
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
Si chiama "zip" perché si considera una sequenza come il lato sinistro di una cerniera e l'altra sequenza come il lato destro della cerniera, e l'operatore zip tirerà i due lati insieme accoppiando i denti (il elementi della sequenza) in modo appropriato.
Esegue l'iterazione attraverso due sequenze e combina i loro elementi, uno per uno, in un'unica nuova sequenza. Quindi prendi un elemento della sequenza A, lo trasformi con l'elemento corrispondente dalla sequenza B e il risultato forma un elemento della sequenza C.
Un modo di pensarci è che è simile Select
, tranne che invece di trasformare elementi da una singola collezione, funziona su due raccolte contemporaneamente.
Dalla articolo di MSDN sul metodo :
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
Se dovessi farlo con un codice imperativo, probabilmente faresti qualcosa del genere:
for (int i = 0; i < numbers.Length && i < words.Length; i++)
{
numbersAndWords.Add(numbers[i] + " " + words[i]);
}
O se LINQ non avesse Zip
in esso, potresti farlo:
var numbersAndWords = numbers.Select(
(num, i) => num + " " + words[i]
);
Ciò è utile quando i dati sono distribuiti in elenchi semplici, simili a array, ciascuno con la stessa lunghezza e ordine e ciascuno che descrive una diversa proprietà dello stesso insieme di oggetti. Zip
ti aiuta a riunire quei dati insieme in una struttura più coerente.
Quindi, se hai una matrice di nomi di stato e un'altra matrice delle loro abbreviazioni, puoi raggrupparle in una State
classe in questo modo:
IEnumerable<State> GetListOfStates(string[] stateNames, int[] statePopulations)
{
return stateNames.Zip(statePopulations,
(name, population) => new State()
{
Name = name,
Population = population
});
}
Select
NON lasciare che il nome Zip
ti butti via. Non ha nulla a che fare con zippare come nel comprimere un file o una cartella (compressione). In realtà prende il nome da come funziona una cerniera sui vestiti: la cerniera sui vestiti ha 2 lati e ogni lato ha un mucchio di denti. Quando si procede in una direzione, la cerniera enumera (viaggia) entrambi i lati e chiude la cerniera serrando i denti. Quando vai nella direzione opposta apre i denti. Si termina con una cerniera aperta o chiusa.
È la stessa idea con il Zip
metodo. Considera un esempio in cui abbiamo due raccolte. Uno contiene lettere e l'altro contiene il nome di un alimento che inizia con quella lettera. Per motivi di chiarezza, li chiamo leftSideOfZipper
e rightSideOfZipper
. Ecco il codice
var leftSideOfZipper = new List<string> { "A", "B", "C", "D", "E" };
var rightSideOfZipper = new List<string> { "Apple", "Banana", "Coconut", "Donut" };
Il nostro compito è quello di produrre una raccolta che abbia la lettera del frutto separata da un :
e il suo nome. Come questo:
A : Apple
B : Banana
C : Coconut
D : Donut
Zip
Al salvataggio. Per tenere il passo con la nostra terminologia con cerniera chiameremo questo risultato closedZipper
e gli elementi della cerniera sinistra che chiameremo leftTooth
e il lato destro che chiameremo righTooth
per ovvi motivi:
var closedZipper = leftSideOfZipper
.Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList();
In quanto sopra stiamo enumerando (viaggiando) il lato sinistro della cerniera e il lato destro della cerniera ed eseguendo un'operazione su ciascun dente. L'operazione che stiamo eseguendo sta concatenando il dente sinistro (lettera dell'alimento) con un :
e poi il dente destro (nome dell'alimento). Lo facciamo usando questo codice:
(leftTooth, rightTooth) => leftTooth + " : " + rightTooth)
Il risultato finale è questo:
A : Apple
B : Banana
C : Coconut
D : Donut
Cosa è successo all'ultima lettera E?
Se stai enumerando (tirando) una vera cerniera per abiti e un lato, non importa il lato sinistro o il lato destro, ha meno denti rispetto all'altro lato, cosa accadrà? Bene la cerniera si fermerà qui. Il Zip
metodo farà esattamente lo stesso: si fermerà quando avrà raggiunto l'ultimo elemento su entrambi i lati. Nel nostro caso il lato destro ha meno denti (nomi di alimenti) quindi si fermerà a "Ciambella".
Non ho i punti rappresentante da pubblicare nella sezione commenti, ma per rispondere alla domanda correlata:
Cosa succede se voglio che zip continui laddove un elenco si esaurisce di elementi? Nel qual caso l'elemento dell'elenco più breve dovrebbe assumere il valore predefinito. L'output in questo caso deve essere A1, B2, C3, D0, E0. - liang 19 novembre 15 alle 3:29
Quello che dovresti fare è usare Array.Resize () per riempire la sequenza più breve con i valori predefiniti e quindi comprimerli () insieme.
Esempio di codice:
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);
Produzione:
A1
B2
C3
D0
E0
Si noti che l'utilizzo di Array.Resize () ha un avvertimento : Redim Preserve in C #?
Se non si conosce quale sequenza sarà la più breve, è possibile creare una funzione che la sussidi:
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());
}
Uscita di semplice .Zip () insieme a ZipDefault ():
A1 A1
B2 B2
C3 C3
D0
E0
Tornando alla risposta principale della domanda originale , un'altra cosa interessante che si potrebbe desiderare di fare (quando le lunghezze delle sequenze da "zippare" sono diverse) è unirle in modo tale che la fine dell'elenco partite al posto della cima. Ciò può essere ottenuto "saltando" il numero appropriato di elementi usando .Skip ().
foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray())
Console.WriteLine(s);
Produzione:
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; } }
Molte delle risposte qui dimostrano Zip
, ma senza spiegare davvero un caso d'uso reale che ne motiverebbe l'uso Zip
.
Un modello particolarmente comune che Zip
è fantastico per iterare su coppie successive di cose. Questo è fatto da un iterazione enumerabile X
con se stesso, saltare 1 elemento: x.Zip(x.Skip(1)
. Esempio visivo:
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)
Queste coppie successive sono utili per trovare le prime differenze tra i valori. Ad esempio, è IEnumable<MouseXPosition>
possibile utilizzare coppie successive di per produrre IEnumerable<MouseXDelta>
. Allo stesso modo, i bool
valori campionati di a button
possono essere interpretati in eventi come NotPressed
/ Clicked
/ Held
/ Released
. Tali eventi possono quindi indirizzare le chiamate a delegare metodi. Ecco un esempio:
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) );
}
}
stampe:
NotPressesd
NotPressesd
NotPressesd
Clicked
Held
Held
Released
Clicked
Released
NotPressesd
Clicked
Come altri hanno già affermato, Zip consente di combinare due raccolte da utilizzare in ulteriori istruzioni Linq o in un ciclo foreach.
Le operazioni che richiedevano un ciclo for e due array ora possono essere eseguite in un ciclo foreach utilizzando un oggetto anonimo.
Un esempio che ho appena scoperto, è un po 'sciocco, ma potrebbe essere utile se la parallelizzazione fosse vantaggiosa sarebbe un attraversamento della coda a riga singola con effetti collaterali:
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 rappresenta gli elementi correnti o dequeued in una coda (l'ultimo elemento viene troncato da Zip). timeSegments.Skip (1) rappresenta gli elementi successivi o sbirciati in una coda. Il metodo Zip combina questi due in un unico oggetto anonimo con una proprietà Next e Current. Quindi filtriamo con Where e apportiamo modifiche con AsParallel (). ForAll. Naturalmente l'ultimo bit potrebbe essere solo una foreach regolare o un'altra istruzione Select che restituisce i segmenti temporali offensivi.
Il metodo Zip consente di "unire" due sequenze non correlate, utilizzando un provider di funzioni di fusione, il chiamante. L'esempio su MSDN è in realtà abbastanza buono nel dimostrare cosa puoi fare con Zip. In questo esempio, prendi due sequenze arbitrarie non correlate e le combini usando una funzione arbitraria (in questo caso, concatenando gli elementi di entrambe le sequenze in un'unica stringa).
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