CodeGym /Kurse /C# SELF /Positionsbasiertes Kombinieren mit

Positionsbasiertes Kombinieren mit Zip

C# SELF
Level 33 , Lektion 4
Verfügbar

1. Einführung

Stell dir zwei Stapel Briefe vor: Der erste – Umschläge mit Namen drauf, der zweite – Postkarten für diese Empfänger. Deine Aufgabe: Jeden Umschlag mit der Postkarte aus derselben Position im zweiten Stapel matchen. Der erste Umschlag – mit der ersten Postkarte, der zweite – mit der zweiten, usw. Du gehst beide Stapel durch, nimmst jeweils ein Element und "zippst" sie zusammen.

In der Programmierung nennt man das zip – wie bei einem Reißverschluss, der zwei Seiten exakt an den Positionen zusammenfügt.

In LINQ ist das ein praktisches und mächtiges Tool, um Daten aus zwei (manchmal auch mehr) Sequenzen zu kombinieren, wenn die Reihenfolge zählt: Erstes Element mit erstem, zweites mit zweitem usw.
Wichtig zu merken: Zip funktioniert nur solange, wie in beiden Collections noch Elemente sind. Wenn eine kürzer ist – wird das Ergebnis auf die Länge der kürzesten abgeschnitten.

Syntax und Funktionsweise

Zip nimmt zwei (oder mehr) Listen und kombiniert sie zu einer neuen: Die Elemente werden nach Index zusammengefügt, also 0 mit 0, 1 mit 1 usw. Wenn eine Liste früher endet, werden keine neuen Paare mehr "gezipped" – das Ergebnis hat die Länge der kürzesten Liste.

Signatur (Standard):


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

Achte darauf: Zip gibt nicht einfach Paare zurück, sondern das, was du im resultSelector angibst. Das kann ein Tuple, ein String, ein Objekt oder sonst was sein – je nach deiner Logik.

  • first und second – die Collections, die kombiniert werden.
  • resultSelector – eine Funktion, die die aktuellen Elemente beider Collections nimmt und ein neues Ergebnis zurückgibt.

Einfach gesagt: Wir gehen beide Collections gleichzeitig durch und machen aus jedem Paar das, was wir brauchen.

2. Beispiele für Zip – von einfach bis komplex

Das einfachste Beispiel: Zwei Zahlen-Arrays addieren

Angenommen, wir haben zwei Listen:

  • Punkte in Mathe
  • Punkte in Literatur
Wir wollen die Gesamtpunktzahl für jeden Studenten an derselben Position berechnen.


// Zwei Arrays mit Punkten der Studenten
int[] mathScores = { 5, 4, 3, 5, 2 };
int[] literatureScores = { 4, 5, 3, 4, 3 };

// Nach Position zusammenfügen und addieren
var totalScores = mathScores.Zip(literatureScores, (math, literature) => math + literature);

foreach (var score in totalScores)
    Console.WriteLine($"Gesamtpunktzahl des Studenten: {score}");

Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 6
Gesamtpunktzahl des Studenten: 9
Gesamtpunktzahl des Studenten: 5

Wie du siehst, alles ganz logisch: Der erste Student bekommt die Summe der ersten Punkte, der zweite die zweiten usw.

Strings und Zahlen kombinieren: Produkte mit Preisen beschriften

Angenommen, du hast eine Liste mit Produkten und eine mit Preisen. Du willst ausgeben: "Produktname — Preis".


string[] productNames = { "Apfel", "Birne", "Banane" };
decimal[] productPrices = { 50.5m, 60.0m, 35.2m };

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

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

Apfel — 50,5 Euro
Birne — 60,0 Euro
Banane — 35,2 Euro

Mit Objekt-Collections arbeiten

Nehmen wir an, wir hatten vorher eine Klasse Student und eine Collection von Studenten. Jetzt gibt es ein separates Array mit Bewertungen, das nach Reihenfolge gematcht wird:


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

List<Student> students = new List<Student>
{
    new Student { Name = "Vasya" },
    new Student { Name = "Petya" },
    new Student { Name = "Masha" }
};

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

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

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

Vasya: Bewertung 7
Petya: Bewertung 9
Masha: Bewertung 8

Komplexere Objekte kombinieren

Im echten Leben muss man oft z.B. zwei Bestelllisten aus verschiedenen Systemen zusammenführen: Jeder Bestellung wird ein Bearbeitungsergebnis oder Status zugeordnet.


string[] orders = { "A-1", "B-2", "C-3" };
string[] statuses = { "Erledigt", "In Bearbeitung", "Abgelehnt" };

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

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

Bestellung A-1: Erledigt
Bestellung B-2: In Bearbeitung
Bestellung C-3: Abgelehnt

4. Wichtige Besonderheiten und typische Fehler

Wenn du Zip benutzt, denk dran: Die Ergebnis-Collection ist so lang wie die kürzeste der Eingaben.
Wenn du z.B. 10 Studenten hast, aber nur 7 Bewertungen, enthält das Ergebnis nur 7 Elemente. Das ist ein häufiger Grund für unerwartete „Datenverluste“.

Fehler Nr. 1: Unterschiedliche Längen oder Reihenfolge der Collections.
Wenn du versehentlich die Reihenfolge der Listen vertauschst oder vergisst, sie vorher so zu sortieren, dass die Elemente zueinander passen, ist das Ergebnis falsch.
Stell dir vor: Die Studentenliste passt nicht zur Reihenfolge der Bewertungen – Vasya bekommt vielleicht Petjas Note. Das ist wie zwei verschiedene Socken anziehen: Jeder ist zwar da, aber das Gefühl ist komisch.

Fehler Nr. 2: Eine der Collections ist leer.
Wenn mindestens eine Liste leer ist, ist das Ergebnis leer.
Zip funktioniert nur, solange in beiden Sequenzen Elemente sind. Wenn eine Quelle keine Daten hat, bekommst du „nichts“.

5. Zip mit mehreren Collections

Ursprünglich konnte LINQ nur zwei Collections kombinieren. Aber ab .NET 6 gibt es eine Überladung, mit der du DREI (ja!) Collections gleichzeitig zippen kannst.

Beispiel mit drei Collections:


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

// Ab .NET 6 gibt's die Überladung:
var characters = names.Zip(planets, ages, (name, planet, age) =>
    $"{name} ({planet}), {age} Jahre alt");

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

Rey (Tatooine), 19 Jahre alt
Luke (Tatooine), 23 Jahre alt
Leia (Alderaan), 19 Jahre alt

Die Anzahl der Elemente im Ergebnis – nach der kürzesten Liste! Wenn ages eins weniger hat, geht der letzte Skywalker "ohne Alter".

6. Zip und LINQ Query Syntax: Gibt's das?

Du hast vielleicht gemerkt, dass wir immer die "Methoden"-Syntax benutzen. In der LINQ Query Syntax gibt es kein eigenes Wort für Zip, und mit from ... in ... geht das nicht. Der Grund: Das Konzept von "zippen" ist positionsbasiertes Kombinieren, aber LINQ-Querys arbeiten mit join, select usw., meist zum Verknüpfen nach Schlüssel, nicht nach Position.

Fazit: Für Zip nehmen wir ausschließlich die Punkt- (also Methoden-)Syntax:


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

Wenn du unbedingt Query-Syntax willst – lass es lieber. Und wenn du es wirklich unbedingt willst, kannst du dir einen eigenen Extension-Method schreiben. 🙂

7. Zip in echten Anwendungen

Beispiel aus deiner Praxis: Alte und neue Preise vergleichen

Angenommen, in unserer Lern-App gibt es eine Produktliste und dazu kommen regelmäßig neue Preise rein. Wir wollen ausgeben, wie sich der Preis jedes Produkts geändert hat:


string[] productNames = { "Apfel", "Banane", "Ananas" };
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}: war {oldPrice}, jetzt {newPrice}, Änderung: {newPrice - oldPrice:+#;-#;0}");

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

Apfel: war 80, jetzt 75, Änderung: -5
Banane: war 30, jetzt 33, Änderung: +3
Ananas: war 110, jetzt 120, Änderung: +10

Praxis: Ergebnisse aus verschiedenen Quellen zusammenführen

In „echten“ Projekten wird Zip oft genutzt, um Ergebnisse aus verschiedenen APIs oder Tabellen zu kombinieren, wenn die Reihenfolge der Daten von außen garantiert ist (z.B. Arrays mit Wetterprognosen und echten Messwerten).

8. Zip in LINQ-Chains: Kombinieren mit anderen Operatoren

Oft wird Zip nicht einzeln, sondern in langen LINQ-Chains verwendet. Zum Beispiel kann man Listen erst filtern, dann zippen und danach eine Statistik berechnen.


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($"Durchschnittliche Gesamtpunktzahl der Bestanden: {avgScore}");

Das Ding ist: Wenn nach Where eine Liste kürzer ist, schneidet Zip das Ergebnis auf die kürzeste zu.

Vergleich von Zip und anderen Kombinationsmethoden

Methode Essenz Kombiniert nach… Ergebnis-Länge…
Zip
Kombiniert Elemente nach Index Index (Position) Kürzeste Collection
Join
Kombiniert nach Schlüssel Gemeinsamer Schlüssel Alle passenden Paare
GroupJoin
Für jeden Schlüssel eine Collection Schlüssel Nach erster Collection
Concat
Hängt Collections hintereinander Einfach ans Ende Summe der Längen

Zip – das ist positionsbasiertes Matching. Join – das ist Matching nach einem gemeinsamen Wert (z.B. Code oder id).

9. Praktische Tipps und typische Fehler

Wenn du Zip in deiner App verwendest, dann prüfe vor allem:

  • Die Reihenfolge der Elemente in beiden Collections stimmt: Sonst „matchst“ du Vasya mit Petjas Noten.
  • Collections sind korrekt sortiert oder vorher für synchrones Durchgehen vorbereitet.
  • Das Ergebnis hat immer die Länge der kürzesten Collection.
  • Benutze Zip nicht, wenn du nach einem Schlüssel matchen willst! Dafür gibt's Join.
  • Noch ein häufiger Fehler: Du hast in einer Collection Duplikate, in der anderen nicht. Oder du hast beim Filtern die Reihenfolge geändert und das Matching verloren.
1
Umfrage/Quiz
Sammlung Zusammenführen, Level 33, Lektion 4
Nicht verfügbar
Sammlung Zusammenführen
Fortgeschrittenes LINQ: Joins und Projektionen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION