2012-03-21 12 views
8

Dado¿Hay una manera fácil de combinar dos secuencias ordenadas usando LINQ?

IEnumerable<T> first; 
IEnumerable<T> second; 

y que tanto first y second están clasificadas por un comparador Func<T, T, int> que devuelve 0 por la igualdad, -1 cuando el primero es "más pequeño" y 1 cuando el segundo es "más pequeño".

¿Existe una forma sencilla de usar LINQ para unir las dos secuencias de forma que la secuencia resultante también sea ordenada por el mismo comparador?

Actualmente estamos usando un algoritmo hecho a mano que funciona, pero la legibilidad de una declaración directa de LINQ sería preferible.

+2

posible duplicado de [algoritmo más eficiente para la fusión ordenados IEnumerable ] (http://stackoverflow.com/questions/ 2767007/most-efficient-algorithm-for-merging-sorted-ienumerablet) – Jon

+0

¿Ha extraído su algoritmo hecho a mano en un método diferente? Esa sería la forma más sencilla de mejorar la legibilidad. – phoog

+2

Si le gusta la legibilidad (sobre el rendimiento), entonces: 'var both = first.Union (second) .OrderBy (comparer);' –

Respuesta

11

Puede definir un método de extensión para esto. Algo así como

public static IEnumerable<T> MergeSorted<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, int> comparer) 
{ 
    using (var firstEnumerator = first.GetEnumerator()) 
    using (var secondEnumerator = second.GetEnumerator()) 
    { 

     var elementsLeftInFirst = firstEnumerator.MoveNext(); 
     var elementsLeftInSecond = secondEnumerator.MoveNext(); 
     while (elementsLeftInFirst || elementsLeftInSecond) 
     { 
      if (!elementsLeftInFirst) 
      { 
        do 
        { 
         yield return secondEnumerator.Current; 
        } while (secondEnumerator.MoveNext()); 
        yield break; 
      } 

      if (!elementsLeftInSecond) 
      { 
        do 
        { 
         yield return firstEnumerator.Current; 
        } while (firstEnumerator.MoveNext()); 
        yield break; 
      } 

      if (comparer(firstEnumerator.Current, secondEnumerator.Current) < 0) 
      { 
       yield return firstEnumerator.Current; 
       elementsLeftInFirst = firstEnumerator.MoveNext(); 
      } 
      else 
      { 
       yield return secondEnumerator.Current; 
       elementsLeftInSecond = secondEnumerator.MoveNext(); 
      } 
     } 
    } 
} 

Uso:

var s1 = new[] { 1, 3, 5, 7, 9 }; 
var s2 = new[] { 2, 4, 6, 6, 6, 8 }; 

var merged = s1.MergeSorted(s2, (a, b) => a > b ? 1 : -1).ToList(); 

Console.WriteLine(string.Join(", ", merged)); 

Salida:

1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9 
+2

Una pieza sólida de código de acceso único - ¡gracias! –

+0

El parámetro 'comparer' podría ser nulo (parámetro opcional) y establecerse en' Comparer .Default.Compare' si es nulo. – springy76

0

Creo que convertir el primer elemento enumerable en una lista y agregar un segundo elemento a esta lista, y luego llamar al género hará el truco.

 IEnumerable<int> first = new List<int>(){1,3}; 
     IEnumerable<int> second = new List<int>(){2,4}; 

     var temp = first.ToList(); 
     temp.AddRange(second); 


     temp.Sort(new Comparison<int>(comparer)); // where comparer is Func<T,T,int> 
+7

Esta implementación es O (n) en almacenamiento adicional, O (n lg n) en el caso típico de tiempo y O (n^2) en el peor caso de tiempo. Hay un algoritmo que es O (1) en almacenamiento adicional y O (n) en el tiempo. –

Cuestiones relacionadas