2009-03-25 10 views
6

Ich habe eine IEnumerable<T> und eine IEnumerable<U>, die ich in eine IEnumerable<KeyValuePair<T,U>> zusammengeführt werden soll, wo die Indizes der Elemente in KeyValuePair zusammengefügt sind die gleichen. Hinweis: Ich verwende nicht IList, daher habe ich keine Zählung oder einen Index für die Elemente, die ich zusammenführe. Wie kann ich das am besten erreichen? Ich würde eine LINQ-Antwort bevorzugen, aber alles, was die Arbeit in einer eleganten Art und Weise erledigt, würde auch funktionieren.Wie kann ich zwei IEnumerables zusammenführen (oder zippen)?

+2

Ab .NET 4.0, kommt der Rahmen mit einem [IEnumerable Zip] (http://msdn.microsoft.com/en-us/library/dd267698%28v=vs.110%29.aspx) Erweiterungsmethode. –

+0

Immer noch [ein anderer Blogbeitrag] (http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx) von Eric Lippert – n8wrl

+0

Lustig - Ich habe gerade diese letzte Nacht gelesen. =) –

Antwort

17

Hinweis: Ab .NET 4.0, das Framework eine .Zip Erweiterung umfasst Verfahren auf IEnumerable, dokumentiert here. Folgendes wird für die Nachwelt und für die Verwendung in der .NET-Framework-Version vor 4.0 beibehalten.

Ich benutze diese Erweiterungsmethoden:

// From http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx 
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> func) { 
    if (first == null) 
     throw new ArgumentNullException("first"); 
    if (second == null) 
     throw new ArgumentNullException("second"); 
    if (func == null) 
     throw new ArgumentNullException("func"); 
    using (var ie1 = first.GetEnumerator()) 
    using (var ie2 = second.GetEnumerator()) 
     while (ie1.MoveNext() && ie2.MoveNext()) 
      yield return func(ie1.Current, ie2.Current); 
} 

public static IEnumerable<KeyValuePair<T, R>> Zip<T, R>(this IEnumerable<T> first, IEnumerable<R> second) { 
    return first.Zip(second, (f, s) => new KeyValuePair<T, R>(f, s)); 
} 

EDIT: Nach den Kommentaren Ich bin verpflichtet, einige Dinge zu klären und zu beheben:

  • Ich habe ursprünglich die erste Zip Implementierung wörtlich aus Bart De Smet's blog
  • Hinzugefügt Enumerator Entsorgung (die auch noted auf Bart ursprünglichen Post) war
  • hinzugefügt Null-Parameterprüfung (auch in Bart Beitrag diskutiert)
+0

Schöner als meine. – erikkallen

+0

Das ist falsch, da IEnumerables die Reihenfolge beibehalten. – Welbog

+0

Sortieren: es ermutigt den _caller_, die Annahme zu machen. Dies ist das einzige, was es tun kann, und manchmal ist die Annahme begründet. –

2

Überlegen Sie, was Sie mehr ein bisschen eng hier sind gefragt:

Sie zwei IEnumerables kombinieren möchten, in denen „die Indizes der Elemente zusammen in der KeyValuePair verbunden sind gleich“, aber sie " haben keine Zählung oderIndex für die Elemente, die ich fusioniere ".

Es gibt keine Garantie, dass Ihre IEnumerables sogar sortiert oder unsortiert sind. Es gibt keine Korrelation zwischen Ihren beiden IEnumerable-Objekten. Wie können Sie also erwarten, dass sie korrelieren?

+0

@welbog: Es sieht so aus, als gäbe es ein Missverständnis der Frage. Ich denke, dass "Erik" mit "Index" die Position des Elements in der IEnumerable (1., 2., etc.) bedeutete –

+0

@mausch: eine Position, die nicht garantiert ist. Abhängig von der Implementierung ist die Reihenfolge der beiden IEnumerables möglicherweise nicht so wie erwartet. – Welbog

+0

@welbog: Wäre es sinnvoll, Zip mit einem solchen Enumerable aufzurufen? Entweder ergibt das keinen Sinn oder der Anrufer muss sich dessen bewusst sein ... Ich sehe keine andere Option. –

0

Ungeprüfte, aber funktionieren soll:

IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> t, IEnumerable<U> u) { 
    IEnumerator<T> et = t.GetEnumerator(); 
    IEnumerator<U> eu = u.GetEnumerator(); 

    for (;;) { 
     bool bt = et.MoveNext(); 
     bool bu = eu.MoveNext(); 
     if (bt != bu) 
      throw new ArgumentException("Different number of elements in t and u"); 
     if (!bt) 
      break; 
     yield return new KeyValuePair<T, U>(et.Current, eu.Current); 
    } 
} 
1

Blick auf nextension:

Derzeit implementierten Methoden

IEnumerable

  • ForEach Führt eine angegebene Aktion für jedes Element des IEnumerable.
  • Clump Gruppiert Elemente in gleiche Größe.
  • Scan Erstellt eine Liste, indem ein Delegat auf Elementpaare in IEnumerable angewendet wird.
  • AtLeast Checks gibt es mindestens eine bestimmte Anzahl von Elementen im IEnumerable.
  • AtMost Checks Es gibt nicht mehr als eine bestimmte Anzahl von Elementen im IEnumerable.
  • Zip Erstellt eine Liste, indem zwei andere Listen zu einer zusammengefasst werden.
  • Zyklus Erstellt eine Liste durch Wiederholung einer anderen Liste.
0

Die MSDN hat folgende Custom Sequence Operators Beispiel. Und Welbog hat recht; Wenn Sie keinen Index zu den zugrunde liegenden Daten haben, können Sie nicht garantieren, dass der Vorgang das tut, was Sie erwarten.

1

würde ich etwas entlang der Linien von verwenden -

IEnumerable<KeyValuePair<T,U>> Merge<T,U>(IEnumerable<T> keyCollection, IEnumerable<U> valueCollection) 
{ 
    var keys = keyCollection.GetEnumerator(); 
    var values = valueCollection.GetEnumerator(); 
    try 
    { 
     keys.Reset(); 
     values.Reset(); 

     while (keys.MoveNext() && values.MoveNext()) 
     { 
      yield return new KeyValuePair<T,U>(keys.Current,values.Current); 
     } 
    } 
    finally 
    { 
     keys.Dispose(); 
     values.Dispose(); 
    } 
} 

Dies sollte richtig danach richtig und Aufräumarbeiten.

+1

Ich denke, es ist eine gute Form, es "Zip" zu nennen, denn das ist eine bekannte Operation in der funktionalen Welt. – Daniel

0

JaredPar hat eine library mit vielen nützlichen Sachen drin, einschließlich Zip, die aktivieren, was Sie tun möchten.

0

Eine weitere Implementierung der functional-dotnet project von Alexey Romanov:

/// <summary> 
/// Takes two sequences and returns a sequence of corresponding pairs. 
/// If one sequence is short, excess elements of the longer sequence are discarded. 
/// </summary> 
/// <typeparam name="T1">The type of the 1.</typeparam> 
/// <typeparam name="T2">The type of the 2.</typeparam> 
/// <param name="sequence1">The first sequence.</param> 
/// <param name="sequence2">The second sequence.</param> 
/// <returns></returns> 
public static IEnumerable<Tuple<T1, T2>> Zip<T1, T2>(
    this IEnumerable<T1> sequence1, IEnumerable<T2> sequence2) { 
    using (
     IEnumerator<T1> enumerator1 = sequence1.GetEnumerator()) 
    using (
     IEnumerator<T2> enumerator2 = sequence2.GetEnumerator()) { 
     while (enumerator1.MoveNext() && enumerator2.MoveNext()) { 
      yield return 
       Pair.New(enumerator1.Current, enumerator2.Current); 
     } 
    } 
    // 
    //zip :: [a] -> [b] -> [(a,b)] 
    //zip (a:as) (b:bs) = (a,b) : zip as bs 
    //zip _  _  = [] 
} 

ersetzen Pair.New mit neuen KeyValuePair<T1, T2> (und dem Rückgabetyp), und Sie sind gut zu gehen.

15

Als Update jemand über diese Frage zu stolpern, .Net 4.0 unterstützt diese nativ als ex von MS:

int[] numbers = { 1, 2, 3, 4 }; 
string[] words = { "one", "two", "three" }; 

var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second); 
Verwandte Themen