2009-03-25 7 views
6

Mam IEnumerable<T> i IEnumerable<U>, które chcę scalić w IEnumerable<KeyValuePair<T,U>>, gdzie indeksy elementów połączonych razem w KeyValuePair są takie same. Uwaga: Nie używam IList, więc nie mam liczby lub indeksu dla scalanych elementów. Jak najlepiej to osiągnąć? Wolałbym odpowiedź LINQ, ale wszystko, co wykonuje pracę w eleganckim stylu, również by działało.Jak scalić (lub zip) dwa IEnumerables?

+2

Począwszy od .NET 4.0, ramy pochodzi z IEnumerable [zip] (http://msdn.microsoft.com/en-us/library/dd267698%28v=vs.110%29.aspx) metoda rozszerzenia. –

+0

Nadal [inny wpis na blogu] (http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx) Eric Lippert – n8wrl

+0

Śmieszne - właśnie przeczytałem tę ostatnią noc. =) –

Odpowiedz

17

Uwaga: W .NET 4.0, ramy zawiera metodę .Zip rozszerzenie na IEnumerable, udokumentowany here. Poniższe informacje są utrzymywane dla potomności i do użytku w wersji platformy .NET wcześniejszej niż 4.0.

używam tych metod rozszerzeń:

// 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: po komentarzach jestem zobowiązany wyjaśnić i naprawić kilka rzeczy:

  • początkowo zajął pierwsze wdrożenie Zip dosłownie z Bart De Smet's blog
  • Dodano moduł wyliczający (który był również noted na oryginalnym wpisie Barta)
  • Dodano sprawdzanie parametrów zerowa (również omówione w poście Barta)
+0

Ładniejsza niż moja. – erikkallen

+0

Jest to błędne, ponieważ zakłada, że ​​IEnumerables zachowują porządek. – Welbog

+0

Rodzaj: zachęca gracza do założenia. To jedyne, co może zrobić, a czasami założenie jest uzasadnione. –

2

Pomyśl o co pytasz trochę dokładniej tutaj:

Chcesz połączyć dwa IEnumerables w którym „indeksy z elementów połączonych ze sobą w KeyValuePair są takie same”, ale " dla elementów, które scalam, nie ma numeru ani nie ma pozycjiindeksu ".

Nie ma gwarancji, że Twoje Iliczniki są nawet posortowane lub nieposortowane. Nie ma żadnej korelacji między Twoimi obiektami IEnumerable, więc jak możesz oczekiwać ich korelacji?

+0

@welbog: Wygląda na to, że jest to nieporozumienie. Myślę, że przez "indeks" Erik oznaczał pozycję elementu w IEnumerable (1st, 2nd, etc) –

+0

@mausch: pozycja, która nie jest gwarantowana. W zależności od implementacji kolejność dwóch IEnumerables może nie być zgodna z oczekiwaniami. – Welbog

+0

@welbog: czy ma sens wywoływanie Zip z taką liczbą przeliczalną? Albo to nie ma sensu, albo dzwoniący musi być tego świadomy ... Nie widzę żadnej innej opcji. –

0

Nietestowane, ale powinno działać:

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

Spójrz na nextension:

realizowanych obecnie Metody

IEnumerable

  • ForEach Wykonuje określone działanie na każdym elemencie IEnumerable.
  • Kumulacja Grupuje elementy na partie o tym samym rozmiarze.
  • Skanowanie Tworzy listę, stosując delegata do par elementów w IEnumerable.
  • AtLeast Checks w IEnumerable znajduje się przynajmniej pewna ilość przedmiotów.
  • AtMost Checks nie ma więcej niż pewna ilość przedmiotów w IEnumerable.
  • Kod pocztowy Tworzy listę, łącząc dwie inne listy w jedną.
  • Cycle Tworzy listę, powtarzając kolejną listę.
0

MSDN ma następujący Custom Sequence Operators przykład. Welbog ma rację; jeśli nie masz indeksu na bazowych danych, nie masz żadnej gwarancji, że operacja zrobi to, co wy sprawdzasz.

1

użyłbym czegoś wzdłuż linii -

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

to powinno działać poprawnie, a potem właściwie porządki.

+1

Myślę, że to dobry sposób nazywać to "zip", ponieważ jest to znana operacja w świecie funkcjonalnym. – Daniel

0

JaredPar ma library z wieloma przydatnymi rzeczami w nim, to Zip, które umożliwiają to, co chcesz zrobić.

0

Kolejna realizacja z functional-dotnet project przez 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 _  _  = [] 
} 

Wymień Pair.New z nowym KeyValuePair<T1, T2> (i typ zwracany) i jesteś dobry, aby przejść.

15

aktualizacji do nikogo natyka to pytanie, .Net 4.0 obsługuje tego natywnie jako ex od MS:

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

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