CodeGym /Cursos /C# SELF /Unir por posición con Zip<...

Unir por posición con Zip

C# SELF
Nivel 33, Lección 4
Disponible

1. Introducción

Imagínate dos pilas de cartas: la primera — con sobres donde están escritos los nombres, la segunda — con postales para esos destinatarios. Tu tarea es emparejar cada sobre con la postal que está en la misma posición en la segunda pila. El primer sobre — con la primera postal, el segundo — con la segunda, y así. Vas cogiendo uno de cada pila y los "zipeas" juntos.

En programación, esta operación se llama zip — como la cremallera, que "engancha" dos lados, emparejándolos estrictamente por posición.

En LINQ es una herramienta cómoda y potente para juntar datos de dos (a veces más) secuencias, cuando el orden importa: el primer elemento con el primero, el segundo con el segundo, etc.
Importante: Zip solo funciona mientras haya elementos en ambas colecciones. Si una es más corta — el resultado se corta por la más corta.

Sintaxis y cómo funciona

Zip coge dos (o más) listas y las une en una nueva: los elementos se emparejan por índice, o sea, el 0 con el 0, el 1 con el 1, etc. Si una lista se acaba antes, ya no se crean más pares — el resultado tiene la longitud de la lista más corta.

Signatura (principal):


IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
    this IEnumerable<TFirst> first,
    IEnumerable<TSecond> second,
    Func<TFirst, TSecond, TResult> resultSelector
)

Ojo: Zip no devuelve solo pares, sino lo que tú pongas en resultSelector. Puede ser una tupla, un string, un objeto, lo que sea — depende de tu lógica.

  • first y second — las colecciones a unir.
  • resultSelector — función que recibe los elementos actuales de ambas colecciones y devuelve el resultado nuevo.

En cristiano: recorremos ambas colecciones a la vez, y en cada paso hacemos con la pareja lo que nos haga falta.

2. Ejemplos de uso de Zip — de lo fácil a lo chungo

El ejemplo más simple: sumar dos arrays de números

Supón que tienes dos listas:

  • Puntuaciones de matemáticas
  • Puntuaciones de literatura
Hay que sacar la puntuación total de cada estudiante en la misma posición.


// Dos arrays con las notas de los estudiantes
int[] mathScores = { 5, 4, 3, 5, 2 };
int[] literatureScores = { 4, 5, 3, 4, 3 };

// Unimos por posición y sumamos
var totalScores = mathScores.Zip(literatureScores, (math, literature) => math + literature);

foreach (var score in totalScores)
    Console.WriteLine($"Puntuación total del estudiante: {score}");

Puntuación total del estudiante: 9
Puntuación total del estudiante: 9
Puntuación total del estudiante: 6
Puntuación total del estudiante: 9
Puntuación total del estudiante: 5

Como ves, todo muy lógico: el primer estudiante recibe la suma de las primeras notas, el segundo — de las segundas, y así.

Combinando strings y números: productos con precios

Supón que tienes una lista de productos y una lista de precios. Hay que mostrar: "ProductName — Price".


string[] productNames = { "Manzana", "Pera", "Plátano" };
decimal[] productPrices = { 50.5m, 60.0m, 35.2m };

var info = productNames.Zip(productPrices, (name, price) => $"{name} — {price} euros");

foreach (var s in info)
    Console.WriteLine(s);

Manzana — 50,5 euros
Pera — 60,0 euros
Plátano — 35,2 euros

Usando con colecciones de objetos

Supón que antes teníamos una clase Student y una colección de estudiantes. Ahora — un array aparte de ratings, emparejado por orden:


public class Student
{
    public string Name { get; set; }
}

List<Student> students = new List<Student>
{
    new Student { Name = "Alice" },
    new Student { Name = "Bob" },
    new Student { Name = "Charlie" }
};

int[] ratings = { 7, 9, 8 };

var studentsWithRatings = students.Zip(ratings, (student, rating) =>
    $"{student.Name}: rating {rating}");

foreach (var s in studentsWithRatings)
    Console.WriteLine(s);

Alice: rating 7
Bob: rating 9
Charlie: rating 8

Unimos objetos más complejos

En la vida real a menudo hay que juntar, por ejemplo, dos listas de pedidos de distintos sistemas: a cada pedido le emparejas el resultado del procesamiento o el estado.


string[] orders = { "A-1", "B-2", "C-3" };
string[] statuses = { "Completado", "En proceso", "Rechazado" };

var orderStatusList = orders.Zip(statuses, (order, status) => new { Order = order, Status = status });

foreach (var os in orderStatusList)
    Console.WriteLine($"Pedido {os.Order}: {os.Status}");

Pedido A-1: Completado
Pedido B-2: En proceso
Pedido C-3: Rechazado

4. Cosas importantes y errores típicos

Cuando uses Zip, recuerda: la colección final tendrá la misma longitud que la más corta de las de entrada.
Por ejemplo, si tienes 10 estudiantes pero solo 7 notas, el resultado tendrá solo 7 elementos. Es una causa típica de "pérdidas" de datos inesperadas.

Error nº1: longitudes o orden de colecciones no coinciden.
Si te equivocas en el orden de las listas o te olvidas de ordenarlas antes para que los elementos correspondan, la pareja final será incorrecta.
Imagínate: la lista de estudiantes no coincide con el orden de las notas — Alice puede recibir la nota de Bob. Es como ponerse calcetines de pares distintos: cada uno en su sitio, pero la sensación es rara.

Error nº2: una de las colecciones está vacía.
Si al menos una lista está vacía, el resultado será vacío.
Zip solo funciona mientras haya elementos en ambas secuencias. Si una fuente no tiene datos, obtienes "nada".

5. Zip con varias colecciones

Al principio LINQ solo permitía unir dos colecciones. Pero desde .NET 6, hay una sobrecarga que permite juntar TRES (¡toma ya!) colecciones a la vez.

Ejemplo con tres colecciones:


string[] names = { "Rei", "Luke", "Leia" };
string[] planets = { "Tatooine", "Tatooine", "Alderaan" };
int[] ages = { 19, 23, 19 };

// En .NET 6+ hay sobrecarga:
var characters = names.Zip(planets, ages, (name, planet, age) =>
    $"{name} ({planet}), {age} años");

foreach (var c in characters)
    Console.WriteLine(c);

Rei (Tatooine), 19 años
Luke (Tatooine), 23 años
Leia (Alderaan), 19 años

¡El número de elementos en el resultado — por la lista más corta! Si ages tiene uno menos, el último Luke se queda "sin edad".

6. Zip y LINQ Query Syntax: ¿existe?

Habrás notado que siempre usamos la sintaxis "de método" (Method Syntax). En LINQ Query Syntax no hay palabra para Zip, y no se puede hacer con from ... in .... La razón es simple: la idea de "zipear" es emparejar por posición, y las queries LINQ funcionan con join, select, etc., normalmente para unir por clave, no por posición.

Moraleja: Para Zip usamos solo la sintaxis de método:


var zipped = collection1.Zip(collection2, (a, b) => ...);

Si te apetece mucho usar query-syntax — mejor no lo hagas. Y si te apetece muchísimo, puedes inventarte tu propio método de extensión. 🙂

7. Usos de Zip en apps reales

Ejemplo práctico: comparar precios antiguos y nuevos

Supón que en nuestra app de estudio hay una lista de productos y de vez en cuando llegan precios nuevos. Hay que mostrar cómo ha cambiado el precio de cada producto:


string[] productNames = { "Manzana", "Plátano", "Piña" };
decimal[] oldPrices = { 80.0m, 30.0m, 110.0m };
decimal[] newPrices = { 75.0m, 33.0m, 120.0m };

var priceChanges = productNames
    .Zip(oldPrices, newPrices, (name, oldPrice, newPrice) => 
        $"{name}: antes {oldPrice}, ahora {newPrice}, cambio: {newPrice - oldPrice:+#;-#;0}");

foreach (var s in priceChanges)
    Console.WriteLine(s);

Manzana: antes 80, ahora 75, cambio: -5
Plátano: antes 30, ahora 33, cambio: +3
Piña: antes 110, ahora 120, cambio: +10

Práctica: unir resultados de distintas fuentes

En proyectos "de verdad" Zip se usa mucho para juntar resultados de distintos APIs o tablas, donde el orden de los datos está garantizado desde fuera (por ejemplo, arrays con previsiones del tiempo y mediciones reales).

8. Zip en cadenas LINQ: combinando con otros operadores

A menudo Zip se usa no solo, sino en cadenas largas de LINQ. Por ejemplo, puedes filtrar listas, luego zipearlas, y después calcular estadísticas finales.


var passedMath = mathScores.Where(x => x >= 3);
var passedLit = literatureScores.Where(x => x >= 3);

var passedPairs = passedMath.Zip(passedLit, (m, l) => m + l);
var avgScore = passedPairs.Average();

Console.WriteLine($"Media de la puntuación total entre los aprobados: {avgScore}");

Truco: si después de Where una lista se ha quedado más corta, Zip recorta el resultado por la más corta.

Comparando Zip y otros métodos de unión

Método Qué hace Une por… Longitud del resultado…
Zip
Une elementos por índice Índice (posición) Colección más corta
Join
Une por clave Clave común Todas las parejas coincidentes
GroupJoin
Para cada grupo de clave — colección Clave Por la primera colección
Concat
Une colecciones una tras otra Simplemente al final Suma de longitudes

Zip — es para correspondencia posicional. Join — para correspondencia por algún valor común (por ejemplo, código o id).

9. Consejos prácticos y errores típicos

Cuando uses Zip en tu app, lo principal — asegúrate de que:

  • El orden de los elementos en ambas colecciones coincide: si no, "mezclas" a Alice con las notas de Bob.
  • Las colecciones están bien ordenadas o preparadas para recorrerlas en paralelo.
  • El resultado siempre será de la longitud de la colección más corta.
  • No uses Zip si necesitas emparejar datos por alguna clave. ¡Para eso está Join!
  • Otro error típico — olvidaste que en una colección hay duplicados y en la otra no. O cambiaste el orden al filtrar y perdiste la correspondencia.
2
Tarea
C# SELF, nivel 33, lección 4
Bloqueada
Lista de productos con precios
Lista de productos con precios
1
Cuestionario/control
Unión de colecciones, nivel 33, lección 4
No disponible
Unión de colecciones
LINQ avanzado: uniones y proyecciones
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION