2011-08-28 9 views
25

Actualmente estoy experimentando un poco con LINQ. Digamos que tengo dos colecciones de idéntica longitud:combinación entrelazada con LINQ?

var first = new string[] { "1", "2", "3" }; 
var second = new string[] { "a", "b", "c" }; 

me gustaría fusionar esas dos colecciones en una sola, pero de forma intercalada. La secuencia resultante debe por lo tanto ser:

"1", "a", "2", "b", "3", "c" 

Lo que he encontrado hasta el momento es una combinación de Zip, un tipo anónimo y SelectMany:

var result = first.Zip(second, (f, s) => new { F = f, S = s }) 
        .SelectMany(fs => new string[] { fs.F, fs.S }); 

¿Alguien sabe de un suplente/sencillo forma de lograr una fusión intercalada con LINQ?

Respuesta

23

Advertencia: esto omitirá los elementos finales si las enumeraciones tienen longitudes diferentes. Si prefiere sustituir en nulos para rellenar la colección más corta, utilice Andrew Shepherd's answer a continuación.


Usted puede escribir su propio método Interleave extensión, como en this example.

internal static IEnumerable<T> InterleaveEnumerationsOfEqualLength<T>(
    this IEnumerable<T> first, 
    IEnumerable<T> second) 
{ 
    using (IEnumerator<T> 
     enumerator1 = first.GetEnumerator(), 
     enumerator2 = second.GetEnumerator()) 
    { 
     while (enumerator1.MoveNext() && enumerator2.MoveNext()) 
     { 
      yield return enumerator1.Current; 
      yield return enumerator2.Current; 
     } 
    } 
} 
+0

Sin duda una buena solución en cuanto a la capacidad de reutilización y legibilidad. – TeaWolf

+2

Creo que si la primera colección es más grande, este código todavía los devolverá a todos, sin embargo, si la segunda colección es más grande, se saltearía. Tal vez vale la pena continuar a través de la segunda colección después del ciclo while para coherencia :-) –

+0

@Danny, sí. Mi preferencia sería detener una vez que se agota el tiempo más corto, entonces no tiene que preocuparse por cómo llenar los vacíos.(Normalmente no pondría el código de otras personas en mis respuestas, pero dejaré la edición de ChaosPandion solo con el código de Jiri.) – Douglas

25

El ejemplo que ya ha proporcionado lata de hecho más simple mediante la supresión del tipo anónimo:

var result = first.Zip(second, (f, s) => new[] { f, s }) 
         .SelectMany(f => f); 
+0

Gracias por la simplificación, parece que estaba haciendo las cosas de la manera más difícil, una vez más. – TeaWolf

+0

No veo más simple que esto :) – MBen

+1

si la primera secuencia tiene 2 elementos y la segunda secuencia tiene 1 elemento, esto producirá 2 elementos. Tenga cuidado con esta implementación: no es una fusión real de las 2 listas. – Denis

4

Usted puede simplemente bucle y seleccione la matriz en función del índice:

var result = 
    Enumerable.Range(0, first.Length * 2) 
    .Select(i => (i % 2 == 0 ? first : second)[i/2]); 
1
var result = first.SelectMany((f, i) => new List<string> { f, second[ i ] }); 
5

La implementación dada en la respuesta aceptada tiene una incoherencia:
La secuencia resultante siempre contendrá todos los elementos de la primera secuencia (debido al ciclo externo while), pero si la segunda secuencia contiene más elementos, estos no se agregarán.

Desde un método Interleave que cabe esperar que la secuencia resultante contiene

  1. sólo 'pares' (duración de la resultante secuencia: min(length_1, length_2) * 2)), o que
  2. los elementos restantes de la secuencia más larga siempre se anexan (longitud de la secuencia resultante: length_1 + length_2).

La siguiente implementación sigue el segundo enfoque.
Tenga en cuenta que el único | en la comparación o que evita la evaluación de cortocircuito.

public static IEnumerable<T> Interleave<T> (
    this IEnumerable<T> first, IEnumerable<T> second) 
{ 
    using (var enumerator1 = first.GetEnumerator()) 
    using (var enumerator2 = second.GetEnumerator()) 
    { 
    bool firstHasMore; 
    bool secondHasMore; 

    while ((firstHasMore = enumerator1.MoveNext()) 
     | (secondHasMore = enumerator2.MoveNext())) 
    { 
     if (firstHasMore) 
     yield return enumerator1.Current; 

     if (secondHasMore) 
     yield return enumerator2.Current; 
    } 
    } 
} 
Cuestiones relacionadas