2012-03-21 9 views
8

DatoC'è un modo semplice per unire due sequenze ordinate usando LINQ?

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

e che entrambi first e second sono ordinate per un operatore di confronto Func<T, T, int> che restituisce 0 per l'uguaglianza, -1 quando il primo è "piccolo" e 1 quando il secondo è "piccolo".

C'è un modo diretto usando LINQ per unire le due sequenze in un modo che rende la sequenza risultante anche ordinata dallo stesso comparatore?

Attualmente stiamo utilizzando un algoritmo realizzato a mano che funziona, ma sarebbe preferibile la leggibilità di una dichiarazione LINQ diretta.

+2

possibile duplicato del [algoritmo più efficiente per la fusione ordinato IEnumerable ] (http://stackoverflow.com/questions/ 2767007/algoritmo più efficiente-per-merging-sorted-ienumerablet) – Jon

+0

Hai estratto l'algoritmo artigianale in un metodo separato? Questo sarebbe il modo più semplice per migliorare la leggibilità. – phoog

+2

Se ti piace la leggibilità (oltre le prestazioni), allora: 'var both = first.Union (second) .OrderBy (comparatore);' –

risposta

11

È possibile definire un metodo di estensione per questo. Qualcosa di simile

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

Usage:

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

uscita:

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

Un pezzo solido di codice a passaggio singolo - grazie! –

+0

Il parametro 'comparer' può essere nullo (parametro opzionale) e impostato su' Comparatore .Default.Compare' se è nullo. – springy76

0

Penso che, convertendo il primo enumerabile in elenco e aggiungendo il secondo elemento a questa lista, chiamare sort sort farà il trucco.

 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

Questa implementazione è O (n) nella memoria aggiuntiva, O (n lg n) nel caso tipico del tempo e O (n^2) nel peggiore dei casi nel tempo. C'è un algoritmo che è sia O (1) nella memoria extra che O (n) nel tempo. –

Problemi correlati