/// <summary> /// Private implementation method that performs a merge of multiple, ordered sequences using /// a precedence function which encodes order-sensitive comparison logic based on the caller's arguments. /// </summary> /// <remarks> /// The algorithm employed in this implementation is not necessarily the most optimal way to merge /// two sequences. A swap-compare version would probably be somewhat more efficient - but at the /// expense of considerably more complexity. One possible optimization would be to detect that only /// a single sequence remains (all other being consumed) and break out of the main while-loop and /// simply yield the items that are part of the final sequence. /// The algorithm used here will perform N*(K1+K2+...Kn-1) comparisons, where <c>N => otherSequences.Count()+1.</c> /// </remarks> private static IEnumerable <T> SortedMergeImpl <T>(Func <T, T, bool> precedenceFunc, IEnumerable <IEnumerable <T> > otherSequences) { using (var disposables = new DisposableGroup <T>(otherSequences.Select(e => e.GetEnumerator()).Acquire())) { var iterators = disposables.Iterators; // prime all of the iterators by advancing them to their first element (if any) // NOTE: We start with the last index to simplify the removal of an iterator if // it happens to be terminal (no items) before we start merging for (var i = iterators.Count - 1; i >= 0; i--) { if (!iterators[i].MoveNext()) { disposables.Exclude(i); } } // while all iterators have not yet been consumed... while (iterators.Count > 0) { var nextIndex = 0; var nextValue = disposables[0].Current; // find the next least element to return for (var i = 1; i < iterators.Count; i++) { var anotherElement = disposables[i].Current; // determine which element follows based on ordering function if (precedenceFunc(nextValue, anotherElement)) { nextIndex = i; nextValue = anotherElement; } } yield return(nextValue); // next value in precedence order // advance iterator that yielded element, excluding it when consumed if (!iterators[nextIndex].MoveNext()) { disposables.Exclude(nextIndex); } } } }
/// <summary> /// Merges two or more sequences that are in a common order (either ascending or descending) into /// a single sequence that preserves that order. /// </summary> /// <typeparam name="TSource">The type of the elements in the sequence</typeparam> /// <param name="source">The primary sequence with which to merge</param> /// <param name="direction">The ordering that all sequences must already exhibit</param> /// <param name="comparer">The comparer used to evaluate the relative order between elements</param> /// <param name="otherSequences">A variable argument array of zero or more other sequences to merge with</param> /// <returns>A merged, order-preserving sequence containing al of the elements of the original sequences</returns> public static IEnumerable <TSource> SortedMerge <TSource>(this IEnumerable <TSource> source, OrderByDirection direction, IComparer <TSource> comparer, params IEnumerable <TSource>[] otherSequences) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (otherSequences == null) { throw new ArgumentNullException(nameof(otherSequences)); } if (otherSequences.Length == 0) { return(source); // optimization for when otherSequences is empty } comparer = comparer ?? Comparer <TSource> .Default; // define an precedence function based on the comparer and direction // this is a function that will return True if (b) should precede (a) var precedenceFunc = direction == OrderByDirection.Ascending ? (Func <TSource, TSource, bool>)((a, b) => comparer.Compare(b, a) < 0) : (a, b) => comparer.Compare(b, a) > 0; // return the sorted merge result return(Impl(new[] { source }.Concat(otherSequences))); // Private implementation method that performs a merge of multiple, ordered sequences using // a precedence function which encodes order-sensitive comparison logic based on the caller's arguments. // // The algorithm employed in this implementation is not necessarily the most optimal way to merge // two sequences. A swap-compare version would probably be somewhat more efficient - but at the // expense of considerably more complexity. One possible optimization would be to detect that only // a single sequence remains (all other being consumed) and break out of the main while-loop and // simply yield the items that are part of the final sequence. // // The algorithm used here will perform N*(K1+K2+...Kn-1) comparisons, where <c>N => otherSequences.Count()+1.</c> IEnumerable <TSource> Impl(IEnumerable <IEnumerable <TSource> > sequences) { using (var disposables = new DisposableGroup <TSource>(sequences.Select(e => e.GetEnumerator()).Acquire())) { var iterators = disposables.Iterators; // prime all of the iterators by advancing them to their first element (if any) // NOTE: We start with the last index to simplify the removal of an iterator if // it happens to be terminal (no items) before we start merging for (var i = iterators.Count - 1; i >= 0; i--) { if (!iterators[i].MoveNext()) { disposables.Exclude(i); } } // while all iterators have not yet been consumed... while (iterators.Count > 0) { var nextIndex = 0; var nextValue = disposables[0].Current; // find the next least element to return for (var i = 1; i < iterators.Count; i++) { var anotherElement = disposables[i].Current; // determine which element follows based on ordering function if (precedenceFunc(nextValue, anotherElement)) { nextIndex = i; nextValue = anotherElement; } } yield return(nextValue); // next value in precedence order // advance iterator that yielded element, excluding it when consumed if (!iterators[nextIndex].MoveNext()) { disposables.Exclude(nextIndex); } } } } }