¿Cuál es el uso del método de extensión Enumerable.Zip en Linq?

¿Cuál es el uso del método de extensión Enumerable.Zip en Linq?

El operador Zip fusiona los elementos correspondientes de dos secuencias usando una función de selector especificada.

 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 es para combinar dos secuencias en una. Por ejemplo, si tienes las secuencias

 1, 2, 3 

y

 10, 20, 30 

y desea la secuencia que es el resultado de multiplicar elementos en la misma posición en cada secuencia para obtener

 10, 40, 90 

tu puedes decir

 var left = new[] { 1, 2, 3 }; var right = new[] { 10, 20, 30 }; var products = left.Zip(right, (m, n) => m * n); 

Se llama “zip” porque usted piensa en una secuencia como el lado izquierdo de una cremallera, y la otra secuencia como el lado derecho de la cremallera, y el operador de la cremallera juntará los dos lados emparejando los dientes (el elementos de la secuencia) apropiadamente.

Se repite a través de dos secuencias y combina sus elementos, uno por uno, en una única secuencia nueva. Entonces tomas un elemento de la secuencia A, lo transformas con el elemento correspondiente de la secuencia B, y el resultado forma un elemento de la secuencia C.

Una forma de pensar es que es similar a Select , excepto que en lugar de transformar elementos de una sola colección, funciona en dos colecciones a la vez.

Del artículo de MSDN sobre el método :

 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 

Si hicieras esto con un código imperativo, probablemente harías algo como esto:

 for (int i = 0; i < numbers.Length && i < words.Length; i++) { numbersAndWords.Add(numbers[i] + " " + words[i]); } 

O si LINQ no tenía Zip , podrías hacer esto:

 var numbersAndWords = numbers.Select( (num, i) => num + " " + words[i] ); 

Esto es útil cuando los datos se extienden en listas simples, tipo array, cada una con la misma longitud y orden, y cada una describe una propiedad diferente del mismo conjunto de objetos. Zip te ayuda a unir esas piezas de datos en una estructura más coherente.

Entonces, si tiene una matriz de nombres de estado y otra matriz de abreviaturas, puede cotejarlos en una clase de State como esta:

 IEnumerable GetListOfStates(string[] stateNames, int[] statePopulations) { return stateNames.Zip(statePopulations, (name, population) => new State() { Name = name, Population = population }); } 

NO DEJE que el nombre Zip lo rechace. No tiene nada que ver con la compresión como al comprimir un archivo o una carpeta (compresión). En realidad, recibe su nombre del funcionamiento de una cremallera en la ropa: la cremallera de la ropa tiene dos lados y cada lado tiene muchos dientes. Cuando va en una dirección, la cremallera enumera (recorre) ambos lados y cierra la cremallera apretando los dientes. Cuando vas en la otra dirección, abre los dientes. Usted termina con una cremallera abierta o cerrada.

Es la misma idea con el método Zip . Considere un ejemplo donde tenemos dos colecciones. Uno tiene letras y el otro contiene el nombre de un alimento que comienza con esa letra. Para mayor claridad los llamo leftSideOfZipper y rightSideOfZipper . Aquí está el código.

 var leftSideOfZipper = new List { "A", "B", "C", "D", "E" }; var rightSideOfZipper = new List { "Apple", "Banana", "Coconut", "Donut" }; 

Nuestra tarea es producir una colección que tenga la letra de la fruta separada por un : y su nombre. Me gusta esto:

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

Zip al rescate. Para mantenerse al día con nuestra terminología de cremallera llamaremos a este resultado closedZipper y los elementos de la cremallera izquierda que llamaremos leftTooth y en el lado derecho llamaremos a righTooth por razones obvias:

 var closedZipper = leftSideOfZipper .Zip(rightSideOfZipper, (leftTooth, rightTooth) => leftTooth + " : " + rightTooth).ToList(); 

En lo anterior, enumeramos (viajando) el lado izquierdo de la cremallera y el lado derecho de la cremallera y realizamos una operación en cada diente. La operación que estamos realizando es concatenar el diente izquierdo (letra de la comida) con un : y luego el diente derecho (nombre del alimento). Hacemos eso usando este código:

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

El resultado final es esto:

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

¿Qué pasó con la última letra E?

Si está enumerando (tirando) una cremallera de ropa real y un lado, no importa el lado izquierdo o el lado derecho, tiene menos dientes que el otro lado, ¿qué pasará? Bueno, la cremallera se detendrá allí. El método Zip hará exactamente lo mismo: se detendrá una vez que haya alcanzado el último elemento de cada lado. En nuestro caso, el lado derecho tiene menos dientes (nombres de alimentos) por lo que se detendrá en “Donut”.

Como han dicho otros, Zip le permite combinar dos colecciones para utilizarlas en otras declaraciones de Linq o en un bucle foreach.

Las operaciones que solían requerir un bucle for y dos matrices ahora se pueden realizar en un bucle foreach utilizando un objeto anónimo.

Un ejemplo que acabo de descubrir, que es un poco tonto, pero podría ser útil si la paralelización fuera beneficioso sería una sola línea Cruce de cola con efectos secundarios:

 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 representa los elementos actuales o descatalogados en una cola (el último elemento está truncado por Zip). timeSegments.Skip (1) representa los siguientes elementos o peek en una cola. El método Zip combina estos dos en un único objeto anónimo con una propiedad Next y Current. Luego filtramos con Dónde y hacemos cambios con AsParallel (). ForAll. Por supuesto, el último bit podría ser un foreach regular u otra instrucción Select que devuelva los segmentos de tiempo ofensivos.

No tengo los puntos de representante para publicar en la sección de comentarios, sino para responder la pregunta relacionada:

¿Qué sucede si quiero que zip continúe donde una lista se quede sin elementos? En cuyo caso, el elemento de la lista más corta debería tomar el valor predeterminado. La salida en este caso es A1, B2, C3, D0, E0. – liang 19 de noviembre de 15 a 3:29

Lo que haría sería utilizar Array.Resize () para rellenar la secuencia más corta con valores predeterminados y luego Zip () juntos.

Ejemplo de código:

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

Salida:

 A1 B2 C3 D0 E0 

Tenga en cuenta que el uso de Array.Resize () tiene una advertencia : Redim Preserve en C #?

Si se desconoce qué secuencia será la más corta, se puede crear una función que la sustente:

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

Salida de .Zip simple () junto con ZipDefault ():

 A1 A1 B2 B2 C3 C3 D0 E0 

Volviendo a la respuesta principal de la pregunta original , otra cosa interesante que uno podría desear hacer (cuando las longitudes de las secuencias para “comprimirse” son diferentes) es unirlas de tal manera que el final de la lista coincide en lugar de la parte superior. Esto se puede lograr “omitiendo” el número apropiado de elementos usando .Skip ().

 foreach (var s in letters.Skip(letters.Length - numbers.Length).Zip(numbers, (l, n) => l + n.ToString()).ToArray()) Console.WriteLine(s); 

Salida:

 C1 D2 E3 

El método Zip le permite “fusionar” dos secuencias no relacionadas, usando un proveedor de funciones de fusión que usted llama. El ejemplo en MSDN es bastante bueno para demostrar lo que puedes hacer con Zip. En este ejemplo, toma dos secuencias arbitrarias, no relacionadas, y las combina utilizando una función arbitraria (en este caso, simplemente concatenar elementos de ambas secuencias en una sola cadena).

 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