2009-03-25 20 views
6

Tengo un IEnumerable<T> y un IEnumerable<U> que quiero fusionar en un IEnumerable<KeyValuePair<T,U>> donde los índices de los elementos unidos en el KeyValuePair son los mismos. Tenga en cuenta que no estoy usando IList, por lo que no tengo un recuento o un índice para los elementos que estoy fusionando. ¿Cuál es la mejor manera de lograr esto? Preferiría una respuesta LINQ, pero cualquier cosa que haga el trabajo de manera elegante también funcionaría.¿Cómo fusiono (o comprime) dos IEnumerables juntos?

+2

A partir de .NET 4.0, el marco viene con un Zip [IEnumerable] (http://msdn.microsoft.com/en-us/library/dd267698%28v=vs.110%29.aspx) método de extensión. –

+0

Todavía [otra publicación del blog] (http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx) por Eric Lippert – n8wrl

+0

Gracioso - Acabo de leer esto anoche. =) –

Respuesta

17

Nota: A partir de .NET 4.0, el marco incluye un método .Zip extensión de IEnumerable, documentado here. Lo siguiente se mantiene para la posteridad y para su uso en la versión de .NET framework anterior a la 4.0.

que utilizan estos métodos de extensión:

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

EDITAR: después de los comentarios que estoy obligado a aclarar y corregir algunas cosas:

  • originalmente tomó la primera aplicación Zip textualmente de Bart De Smet's blog
  • Agregado de disposición de enumerador (que también era noted en la publicación original de Bart)
  • Añadido comprobación parámetro nulo (también discutido en el post de Bart)
+0

Más bonito que el mío. – erikkallen

+0

Esto es incorrecto ya que supone que IEnumerables conservará el orden. – Welbog

+0

Tipo de: anima al _caller_ a hacer la suposición. Esto hace lo único que puede hacer, y a veces la suposición está bien fundada. –

2

pensar en lo que estás pidiendo un poco más de cerca aquí:

desea combinar dos IEnumerables en la que "los índices de los elementos unidos entre sí en el KeyValuePair son los mismos", pero que " no tiene un conteo o uníndice para los artículos que estoy fusionando ".

No hay garantía de que sus IEnumerables estén ordenados o no. No existe una correlación entre sus dos objetos IEnumerable, entonces, ¿cómo puede esperar correlacionarlos?

+0

@welbog: Parece que hay un malentendido de la pregunta. Creo que por "índice" Erik significaba la posición del elemento en el IEnumerable (1º, 2º, etc.) –

+0

@mausch: una posición que no está garantizada. Dependiendo de la implementación, el orden de los dos IEnumerables podría no ser el esperado. – Welbog

+0

@welbog: ¿tendría sentido llamar Zip con un enumerable? O no tiene sentido o la persona que llama tiene que ser consciente de esto ... No veo ninguna otra opción. –

0

no probado, pero debería funcionar:

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

Mira nextension:

métodos actualmente implementadas

IEnumerable

  • ParaCada Realiza una acción especificada en cada elemento de la interfaz IEnumerable.
  • Clump Agrupa los artículos en lotes del mismo tamaño.
  • Escaneo Crea una lista aplicando un delegado a pares de elementos en IEnumerable.
  • Comprobaciones al menos Hay al menos una cierta cantidad de elementos en el IEnumerable.
  • En la mayoría de los cheques no hay más de una cierta cantidad de artículos en el IEnumerable.
  • Código postal Crea una lista combinando otras dos listas en una sola.
  • Ciclo Crea una lista repitiendo otra lista.
0

El MSDN tiene la Custom Sequence Operators ejemplo siguiente. Y Welbog tiene razón; si no tiene un índice sobre los datos subyacentes, no tiene garantía de que la operación haga lo que espera.

1

me gustaría utilizar algo en la línea de -

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

Esto debería funcionar correctamente, y la limpieza adecuada después.

+1

Creo que es bueno llamarlo "zip", ya que es una operación conocida en el mundo funcional. – Daniel

0

JaredPar tiene un library con un montón de cosas útiles en él, incluye Zip que permitirá lo que quieras hacer.

0

Otra aplicación de la functional-dotnet project por 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 _  _  = [] 
} 

Reemplazar Pair.New con el nuevo KeyValuePair<T1, T2> (y el tipo de retorno) y ya está bueno para ir.

15

como una actualización a cualquiera que tropezarse con esta pregunta, .Net 4.0 soporta de forma nativa como ex desde MS:

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

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