Подтвердить что ты не робот

Самый эффективный алгоритм для слияния отсортированного IEnumerable <T>

У меня есть несколько огромных отсортированных перечислимых последовательностей, которые я хочу объединить. Списки тезисов обрабатываются как IEnumerable, но уже отсортированы. Поскольку списки входных данных сортируются, должно быть возможно объединить их в одну поездку без повторной сортировки.

Я хотел бы сохранить отложенное поведение выполнения.

Я попытался написать наивный алгоритм, который делает это (см. ниже). Однако это выглядит довольно уродливо, и я уверен, что его можно оптимизировать. Он может существовать более академическим алгоритмом...

IEnumerable<T> MergeOrderedLists<T, TOrder>(IEnumerable<IEnumerable<T>> orderedlists, 
                                            Func<T, TOrder> orderBy)
{
    var enumerators = orderedlists.ToDictionary(l => l.GetEnumerator(), l => default(T));
    IEnumerator<T> tag = null;

    var firstRun = true;
    while (true)
    {
        var toRemove = new List<IEnumerator<T>>();
        var toAdd = new List<KeyValuePair<IEnumerator<T>, T>>();
        foreach (var pair in enumerators.Where(pair => firstRun || tag == pair.Key))
        {
            if (pair.Key.MoveNext())
                toAdd.Add(pair);
            else
                toRemove.Add(pair.Key);
        }

        foreach (var enumerator in toRemove)
            enumerators.Remove(enumerator);

        foreach (var pair in toAdd)
            enumerators[pair.Key] = pair.Key.Current;

        if (enumerators.Count == 0)
            yield break;

        var min = enumerators.OrderBy(t => orderBy(t.Value)).FirstOrDefault();
        tag = min.Key;
        yield return min.Value;

        firstRun = false;
    }
}

Метод можно использовать следующим образом:

// Person lists are already sorted by age
MergeOrderedLists(orderedList, p => p.Age);

при условии, что существует следующий Person класс:

    public class Person
    {
        public int Age { get; set; }
    }

Дубликаты должны быть сохранены, нас не волнует их порядок в новой последовательности. Вы видите какую-либо очевидную оптимизацию, которую я мог бы использовать?

4b9b3361

Ответ 1

Вот мой четвертый (спасибо @tanascius за то, что он подтолкнул это к чему-то значительно большему LINQ):

public static IEnumerable<T> MergePreserveOrder3<T, TOrder>(
    this IEnumerable<IEnumerable<T>> aa,
    Func<T, TOrder> orderFunc)
where TOrder : IComparable<TOrder>
{
    var items = aa.Select(xx => xx.GetEnumerator()).Where(ee => ee.MoveNext())
        .OrderBy(ee => orderFunc(ee.Current)).ToList();

    while (items.Count > 0)
    {
        yield return items[0].Current;

        var next = items[0];
        items.RemoveAt(0);
        if (next.MoveNext())
        {
            // simple sorted linear insert
            var value = orderFunc(next.Current);
            var ii = 0;
            for ( ; ii < items.Count; ++ii)
            {
                if (value.CompareTo(orderFunc(items[ii].Current)) <= 0)
                {
                    items.Insert(ii, next);
                    break;
                }
            }

            if (ii == items.Count) items.Add(next);
        }
        else next.Dispose(); // woops! can't forget IDisposable
    }
}

Результаты:

for (int p = 0; p < people.Count; ++p)
{
    Console.WriteLine("List {0}:", p + 1);
    Console.WriteLine("\t{0}", String.Join(", ", people[p].Select(x => x.Name)));
}

Console.WriteLine("Merged:");
foreach (var person in people.MergePreserveOrder(pp => pp.Age))
{
    Console.WriteLine("\t{0}", person.Name);
}

List 1:
        8yo, 22yo, 47yo, 49yo
List 2:
        35yo, 47yo, 60yo
List 3:
        28yo, 55yo, 64yo
Merged:
        8yo
        22yo
        28yo
        35yo
        47yo
        47yo
        49yo
        55yo
        60yo
        64yo

Улучшена поддержка .NET 4.0 Tuple:

public static IEnumerable<T> MergePreserveOrder4<T, TOrder>(
    this IEnumerable<IEnumerable<T>> aa,
    Func<T, TOrder> orderFunc) where TOrder : IComparable<TOrder>
{
    var items = aa.Select(xx => xx.GetEnumerator())
                  .Where(ee => ee.MoveNext())
                  .Select(ee => Tuple.Create(orderFunc(ee.Current), ee))
                  .OrderBy(ee => ee.Item1).ToList();

    while (items.Count > 0)
    {
        yield return items[0].Item2.Current;

        var next = items[0];
        items.RemoveAt(0);
        if (next.Item2.MoveNext())
        {
            var value = orderFunc(next.Item2.Current);
            var ii = 0;
            for (; ii < items.Count; ++ii)
            {
                if (value.CompareTo(items[ii].Item1) <= 0)
                {   // NB: using a tuple to minimize calls to orderFunc
                    items.Insert(ii, Tuple.Create(value, next.Item2));
                    break;
                }
            }

            if (ii == items.Count) items.Add(Tuple.Create(value, next.Item2));
        }
        else next.Item2.Dispose(); // woops! can't forget IDisposable
    }
}

Ответ 2

Можно предположить, что это может улучшить ясность и производительность:

  • Создайте очередь приоритета над парами T, IEnumerable<T>, упорядоченными по вашей функции сравнения на T
  • При объединении каждого IEnumerable<T> добавьте элемент в очередь приоритетов, аннотированную ссылкой на IEnumerable<T>, где он был создан
  • Пока очередь приоритетов не пуста
    • Извлечь минимальный элемент из очереди приоритетов
    • Продвиньте IEnumerable<T> в своей аннотации следующему элементу
    • Если MoveNext() возвращено true, добавьте следующий элемент в очередь приоритетов, аннотированную ссылкой на IEnumerable<T> только что продвинутый
    • Если MoveNext() возвращает false, не добавляйте ничего в очередь приоритетов
    • Убрать элемент с выделенным слоем

Ответ 3

Вот решение, которое имеет очень хороший анализ сложности и значительно короче, чем другие предлагаемые решения.

public static IEnumerable<T> Merge<T>(this IEnumerable<IEnumerable<T>> self) 
    where T : IComparable<T>
{
    var es = self.Select(x => x.GetEnumerator()).Where(e => e.MoveNext());
    var tmp = es.ToDictionary(e => e.Current);
    var dict = new SortedDictionary<T, IEnumerator<T>>(tmp);
    while (dict.Count > 0)
    {
        var key = dict.Keys.First();
        var cur = dict[key];
        dict.Remove(key);
        yield return cur.Current;
        if (cur.MoveNext())
            dict.Add(cur.Current, cur);                    
    }
}

Ответ 4

Сколько списков вы планируете объединить? Похоже, ваш алгоритм не будет эффективен, если у вас будет много разных списков для слияния. Эта строка является проблемой:

var min = enumerators.OrderBy(t => orderBy(t.Value)).FirstOrDefault();

Это будет выполняться один раз для каждого элемента во всех списках, поэтому ваша среда выполнения будет O (n * m), где n - это ВСЕГО количество элементов во всех списках, а n - количество списков. Выраженная в терминах средней длины списка в списке списков, время выполнения равно O (a * m ^ 2).

Если вам понадобится объединить множество списков, я бы предложил использовать heap. Затем каждую итерацию вы можете удалить наименьшее значение из кучи и добавить следующий элемент в кучу из списка, из которого было получено самое маленькое значение.

Ответ 5

Здесь решение с NO SORTING... просто минимальное количество сравнений. (Я пропустил фактическую последовательность функций для простоты). Обновлено для построения сбалансированного дерева: -

    /// <summary>
    /// Merge a pair of ordered lists
    /// </summary>
    public static IEnumerable<T> Merge<T>(IEnumerable<T> aList, IEnumerable<T> bList)
        where T:IComparable<T>
    {
        var a = aList.GetEnumerator();
        bool aOK = a.MoveNext();

        foreach (var b in bList)
        {
            while (aOK && a.Current.CompareTo(b) <= 0) {yield return a.Current; aOK = a.MoveNext();}
            yield return b;
        }
        // And anything left in a
        while (aOK) { yield return a.Current; aOK = a.MoveNext(); }
    }

    /// <summary>
    /// Merge lots of sorted lists
    /// </summary>
    public static IEnumerable<T> Merge<T>(IEnumerable<IEnumerable<T>> listOfLists)
        where T : IComparable<T>
    {
        int n = listOfLists.Count();
        if (n < 2) 
            return listOfLists.FirstOrDefault();
        else
            return Merge (Merge(listOfLists.Take(n/2)), Merge(listOfLists.Skip(n/2)));
    }


public static void Main(string[] args)
{

    var sample = Enumerable.Range(1, 5).Select((i) => Enumerable.Range(i, i+5).Select(j => string.Format("Test {0:00}", j)));

    Console.WriteLine("Merged:");
    foreach (var result in Merge(sample))
    {
        Console.WriteLine("\t{0}", result);
    }

Ответ 6

Вот мое решение:
Алгоритм берет первый элемент каждого списка и помещает их в небольшой вспомогательный класс (отсортированный список, который принимает mutliple элементы с одинаковым значением). В этом отсортированном списке используется двоичная вставка.
Итак, первый элемент в этом списке - это элемент, который мы хотим вернуть. После этого мы удалим его из отсортированного списка и вставим следующий элемент из исходного исходного списка (по крайней мере, пока этот список содержит больше элементов). Опять же, мы можем вернуть первый элемент нашего отсортированного списка. Когда отсортированный список пуст один раз, мы использовали весь элемент из всех разных исходных списков и выполняем.

В этом решении используются меньше foreach операторов и OrderBy на каждом шаге, что должно улучшить поведение во время выполнения. Только двоичная вставка должна быть сделана снова и снова.

IEnumerable<T> MergeOrderedLists<T, TOrder>( IEnumerable<IEnumerable<T>> orderedlists, Func<T, TOrder> orderBy )
{
    // Get an enumerator for each list, create a sortedList
    var enumerators = orderedlists.Select( enumerable => enumerable.GetEnumerator() );
    var sortedEnumerators = new SortedListAllowingDoublets<TOrder, IEnumerator<T>>();

    // Point each enumerator onto the first element
    foreach( var enumerator in enumerators )
    {
        // Missing: assert true as the return value
        enumerator.MoveNext();

        //  Initially add the first value
        sortedEnumerators.AddSorted( orderBy( enumerator.Current ), enumerator );
    }

    // Continue as long as we have elements to return
    while( sortedEnumerators.Count != 0 )
    {
        // The first element of the sortedEnumerator list always
        // holds the next element to return
        var enumerator = sortedEnumerators[0].Value;

        // Return this enumerators current value
        yield return enumerator.Current;

        // Remove the element we just returned
        sortedEnumerators.RemoveAt( 0 );

        // Check if there is another element in the list of the enumerator
        if( enumerator.MoveNext() )
        {
            // Ok, so add it to the sorted list
            sortedEnumerators.AddSorted( orderBy( enumerator.Current ), enumerator );
        }
    }

Мой вспомогательный класс (с использованием простой двоичной вставки):

private class SortedListAllowingDoublets<TOrder, T> : Collection<KeyValuePair<TOrder, T>> where T : IEnumerator
{
    public void AddSorted( TOrder value, T enumerator )
    {
        Insert( GetSortedIndex( value, 0, Count - 1 ), new KeyValuePair<TOrder, T>( value, enumerator ) );
    }

    private int GetSortedIndex( TOrder item, int startIndex, int endIndex )
    {
        if( startIndex > endIndex )
        {
            return startIndex;
        }
        var midIndex = startIndex + ( endIndex - startIndex ) / 2;
        return Comparer<TOrder>.Default.Compare( this[midIndex].Key, item ) < 0 ? GetSortedIndex( item, midIndex + 1, endIndex ) : GetSortedIndex( item, startIndex, midIndex - 1 );
    }
}

Что не реализовано прямо сейчас: проверьте пустой список, который вызовет проблемы.
И класс SortedListAllowingDoublets может быть улучшен, чтобы взять компаратор вместо использования Comparer<TOrder>.Default самостоятельно.

Ответ 7

Моя версия sixlettervariables ответит. Я уменьшил количество вызовов orderFunc (каждый элемент только проходит через orderFunc один раз), а в случае связей сортировка пропускается. Это оптимизировано для небольшого количества источников, большего количества элементов внутри каждого источника и, возможно, дорогого orderFunc.

public static IEnumerable<T> MergePreserveOrder<T, TOrder>(
  this IEnumerable<IEnumerable<T>> sources, 
  Func<T, TOrder> orderFunc)  
  where TOrder : IComparable<TOrder> 
{
  Dictionary<TOrder, List<IEnumerable<T>>> keyedSources =
    sources.Select(source => source.GetEnumerator())
      .Where(e => e.MoveNext())
      .GroupBy(e => orderFunc(e.Current))
      .ToDictionary(g => g.Key, g => g.ToList()); 

  while (keyedSources.Any())
  {
     //this is the expensive line
    KeyValuePair<TOrder, List<IEnumerable<T>>> firstPair = keyedSources
      .OrderBy(kvp => kvp.Key).First();

    keyedSources.Remove(firstPair.Key);
    foreach(IEnumerable<T> e in firstPair.Value)
    {
      yield return e.Current;
      if (e.MoveNext())
      {
        TOrder newKey = orderFunc(e.Current);
        if (!keyedSources.ContainsKey(newKey))
        {
          keyedSources[newKey] = new List<IEnumerable<T>>() {e};
        }
        else
        {
          keyedSources[newKey].Add(e);
        }
      }
    }
  }
}

Я уверен, что это может быть дополнительно улучшено в SortedDictionary, но я не достаточно смел, чтобы попробовать решение, используя один без редактора.

Ответ 8

Вот дружественное решение Linq, основанное на Wintellect OrderedBag:

public static IEnumerable<T> MergeOrderedLists<T, TOrder>(this IEnumerable<IEnumerable<T>> orderedLists, Func<T, TOrder> orderBy)
    where TOrder : IComparable<TOrder>
{
    var enumerators = new OrderedBag<IEnumerator<T>>(orderedLists
        .Select(enumerable => enumerable.GetEnumerator())
        .Where(enumerator => enumerator.MoveNext()),
        (x, y) => orderBy(x.Current).CompareTo(orderBy(y.Current)));
    while (enumerators.Count > 0)
    {
        IEnumerator<T> minEnumerator = enumerators.RemoveFirst();
        T minValue = minEnumerator.Current;
        if (minEnumerator.MoveNext())
            enumerators.Add(minEnumerator);
        else
            minEnumerator.Dispose();
        yield return minValue;
    }
}

Если вы используете любое решение на основе Enumerator, не забудьте вызвать Dispose()

И вот простой тест:

[Test]
public void ShouldMergeInOrderMultipleOrderedListWithDuplicateValues()
{
    // given
    IEnumerable<IEnumerable<int>> orderedLists = new[]
    {
        new [] {1, 5, 7},
        new [] {1, 2, 4, 6, 7}
    };

    // test
    IEnumerable<int> merged = orderedLists.MergeOrderedLists(i => i);

    // expect
    merged.ShouldAllBeEquivalentTo(new [] { 1, 1, 2, 4, 5, 6, 7, 7 });
}

Ответ 9

Это выглядит как ужасно полезная функция, поэтому я решил нанести удар. Мой подход очень похож на heightechrider, так как он разбивает проблему на объединение двух отсортированных IEnumerables в один, а затем принимает это и объединяет его со следующим в списке. Скорее всего, есть некоторые оптимизации, которые вы можете сделать, но она работает с моей простой тестовой программой:

      public static IEnumerable<T> mergeSortedEnumerables<T>(
            this IEnumerable<IEnumerable<T>> listOfLists, 
            Func<T, T, Boolean> func)
      {
            IEnumerable<T> l1 = new List<T>{};
            foreach (var l in listOfLists)
            {
                 l1 = l1.mergeTwoSorted(l, func);
            }

            foreach (var t in l1)
            {
                 yield return t;
            }
      }

      public static IEnumerable<T> mergeTwoSorted<T>(
            this IEnumerable<T> l1, 
            IEnumerable<T> l2, 
            Func<T, T, Boolean> func)
      {
            using (var enumerator1 = l1.GetEnumerator())
            using (var enumerator2 = l2.GetEnumerator())
            {
                 bool enum1 = enumerator1.MoveNext();
                 bool enum2 = enumerator2.MoveNext();
                 while (enum1 || enum2)
                 {
                      T t1 = enumerator1.Current;
                      T t2 = enumerator2.Current;

                      //if they are both false
                      if (!enum1 && !enum2)
                      {
                            break;
                      }
                      //if enum1 is false
                      else if (!enum1)
                      {
                            enum2 = enumerator2.MoveNext();
                            yield return t2;

                      }
                      //if enum2 is false
                      else if (!enum2)
                      {
                            enum1 = enumerator1.MoveNext();
                            yield return t1;

                      }
                      //they are both true
                      else
                      {
                            //if func returns true then t1 < t2
                            if (func(t1, t2))
                            {
                                 enum1 = enumerator1.MoveNext();
                                 yield return t1;

                            }
                            else
                            {
                                 enum2 = enumerator2.MoveNext();
                                 yield return t2;

                            }
                      }
                 }
            }
      }

Затем, чтобы проверить это:

                List<int> ws = new List<int>() { 1, 8, 9, 16, 17, 21 };
                List<int> xs = new List<int>() { 2, 7, 10, 15, 18 };
                List<int> ys = new List<int>() { 3, 6, 11, 14 };
                List<int> zs = new List<int>() { 4, 5, 12, 13, 19, 20 };
                List<IEnumerable<int>> lss = new List<IEnumerable<int>> { ws, xs, ys, zs };

                foreach (var v in lss.mergeSortedEnumerables(compareInts))
                {
                     Console.WriteLine(v);
                }

Ответ 10

Мне задали этот вопрос в качестве вопроса интервью сегодня вечером, и у меня не было отличного ответа в течение 20 или около того минут. Поэтому я заставил себя написать алгоритм без каких-либо поисков. Ограничение заключалось в том, что входы уже были отсортированы. Здесь мой код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Merger
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] a = { 1, 3, 6, 102, 105, 230 };
      int[] b = { 101, 103, 112, 155, 231 };

      var mm = new MergeMania();

      foreach(var val in mm.Merge<int>(a, b))
      {
        Console.WriteLine(val);
      }
      Console.ReadLine();
    }
  }

  public class MergeMania
  {
    public IEnumerable<T> Merge<T>(params IEnumerable<T>[] sortedSources) 
      where T : IComparable
    {
      if (sortedSources == null || sortedSources.Length == 0) 
        throw new ArgumentNullException("sortedSources");

      //1. fetch enumerators for each sourc
      var enums = (from n in sortedSources 
             select n.GetEnumerator()).ToArray();

      //2. fetch enumerators that have at least one value
      var enumsWithValues = (from n in enums 
                   where n.MoveNext() 
                   select n).ToArray();
      if (enumsWithValues.Length == 0) yield break; //nothing to iterate over

      //3. sort by current value in List<IEnumerator<T>>
      var enumsByCurrent = (from n in enumsWithValues 
                  orderby n.Current 
                  select n).ToList();
      //4. loop through
      while (true)
      {
        //yield up the lowest value
        yield return enumsByCurrent[0].Current;

        //move the pointer on the enumerator with that lowest value
        if (!enumsByCurrent[0].MoveNext())
        {
          //remove the first item in the list
          enumsByCurrent.RemoveAt(0);

          //check for empty
          if (enumsByCurrent.Count == 0) break; //we're done
        }
        enumsByCurrent = enumsByCurrent.OrderBy(x => x.Current).ToList();
      }
    }
  }
}

Надеюсь, что это поможет.

Ответ 11

Попытка улучшить @cdiggins ответ. Эта реализация работает правильно, если два элемента, которые сравниваются как равные, содержатся в двух разных последовательностях (например, не имеют недостатков, упомянутых @ChadHenderson).

Алгоритм описан в Википедии, сложность O (m log n), где n - количество списков, которые сливаются и m - сумма длин списков.

OrderedBag<T> из Wintellect.PowerCollections используется вместо очереди с приоритетом кучи, но это не меняет сложность.

public static IEnumerable<T> Merge<T>(
   IEnumerable<IEnumerable<T>> listOfLists,
   Func<T, T, int> comparison = null)
{
   IComparer<T> cmp = comparison != null
      ? Comparer<T>.Create(new Comparison<T>(comparison))
      : Comparer<T>.Default;
   List<IEnumerator<T>> es = listOfLists
      .Select(l => l.GetEnumerator())
      .Where(e => e.MoveNext())
      .ToList();
   var bag = new OrderedBag<IEnumerator<T>>(
      (e1, e2) => cmp.Compare(e1.Current, e2.Current));
   es.ForEach(e => bag.Add(e));
   while (bag.Count > 0)
   {
      IEnumerator<T> e = bag.RemoveFirst();
      yield return e.Current;
      if (e.MoveNext())
      {
         bag.Add(e);
      }
   }
}

Ответ 12

Каждый объединенный список должен быть уже отсортирован. Этот метод найдет равные элементы по порядку их списков. Например, если элементы Ti == Tj и они соответственно из списка я и списка j (i < j), то Ti будет перед Tj в объединенном результате. Сложность O (mn), где n - количество списков, которые сливаются, а m - сумма длин списков.

public static IEnumerable<T> Merge<T, TOrder>(this IEnumerable<IEnumerable<T>> TEnumerable_2, Func<T, TOrder> orderFunc, IComparer<TOrder> cmp=null)
{
    if (cmp == null)
    {
        cmp = Comparer<TOrder>.Default;
    }

    List<IEnumerator<T>> TEnumeratorLt = TEnumerable_2
       .Select(l => l.GetEnumerator())
       .Where(e => e.MoveNext())
       .ToList();

    while (TEnumeratorLt.Count > 0)
    {
        int intMinIndex;
        IEnumerator<T> TSmallest = TEnumeratorLt.GetMin(TElement => orderFunc(TElement.Current), out intMinIndex, cmp);
        yield return TSmallest.Current;

        if (TSmallest.MoveNext() == false)
        {
            TEnumeratorLt.RemoveAt(intMinIndex);
        }
    }
}

/// <summary>
/// Get the first min item in an IEnumerable, and return the index of it by minIndex
/// </summary>
public static T GetMin<T, TOrder>(this IEnumerable<T> self, Func<T, TOrder> orderFunc, out int minIndex, IComparer<TOrder> cmp = null)
{
    if (self == null) throw new ArgumentNullException("self");            

    IEnumerator<T> selfEnumerator = self.GetEnumerator();
    if (!selfEnumerator.MoveNext()) throw new ArgumentException("List is empty.", "self");

    if (cmp == null) cmp = Comparer<TOrder>.Default;

    T min = selfEnumerator.Current;
    minIndex = 0;
    int intCount = 1;
    while (selfEnumerator.MoveNext ())
    {
        if (cmp.Compare(orderFunc(selfEnumerator.Current), orderFunc(min)) < 0)
        {
            min = selfEnumerator.Current;
            minIndex = intCount;
        }
        intCount++;
    }

    return min;
}

Ответ 13

Я использовал более функциональный подход, надеюсь, что это хорошо читается.

Прежде всего, это сам метод слияния:

public static IEnumerable<T> MergeSorted<T>(IEnumerable<IEnumerable<T>> xss) where T :IComparable
{
    var stacks = xss.Select(xs => new EnumerableStack<T>(xs)).ToList();

    while (true)
    {
        if (stacks.All(x => x.IsEmpty)) yield break;

        yield return 
            stacks
                .Where(x => !x.IsEmpty)
                .Select(x => new { peek = x.Peek(), x })
                .MinBy(x => x.peek)
                .x.Pop();
    }
}

Идея состоит в том, что мы превращаем каждый IEnumerable в EnumerableStack, который имеет члены Peek(), Pop() и IsEmpty.

Он работает точно так же, как обычный стек. Обратите внимание, что вызов IsEmpty может перечислить завернутый IEnumerable.

Вот код:

/// <summary>
/// Wraps IEnumerable in Stack like wrapper
/// </summary>
public class EnumerableStack<T>
{
    private enum StackState
    {
        Pending,
        HasItem,
        Empty
    }

    private readonly IEnumerator<T> _enumerator;

    private StackState _state = StackState.Pending;

    public EnumerableStack(IEnumerable<T> xs)
    {
        _enumerator = xs.GetEnumerator();
    }

    public T Pop()
    {
        var res = Peek(isEmptyMessage: "Cannot Pop from empty EnumerableStack");
        _state = StackState.Pending;
        return res;
    }

    public T Peek()
    {
        return Peek(isEmptyMessage: "Cannot Peek from empty EnumerableStack");
    }

    public bool IsEmpty
    {
        get
        {
            if (_state == StackState.Empty) return true;
            if (_state == StackState.HasItem) return false;
            ReadNext();
            return _state == StackState.Empty;
        }
    }

    private T Peek(string isEmptyMessage)
    {
        if (_state != StackState.HasItem)
        {
            if (_state == StackState.Empty) throw new InvalidOperationException(isEmptyMessage);
            ReadNext();
            if (_state == StackState.Empty) throw new InvalidOperationException(isEmptyMessage);
        }
        return _enumerator.Current;
    }

    private void ReadNext()
    {
        _state = _enumerator.MoveNext() ? StackState.HasItem : StackState.Empty;
    }
}

Наконец, вот метод расширения MinBy в случае, если он еще не написал один из них:

public static T MinBy<T, TS>(this IEnumerable<T> xs, Func<T, TS> selector) where TS : IComparable
{
    var en = xs.GetEnumerator();
    if (!en.MoveNext()) throw new Exception();

    T max = en.Current;
    TS maxVal = selector(max);
    while(en.MoveNext())
    {
        var x = en.Current;
        var val = selector(x);
        if (val.CompareTo(maxVal) < 0)
        {
            max = x;
            maxVal = val;
        }
    }

    return max;
}

Ответ 14

Это альтернативное решение:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Text.RegularExpressions;

namespace ConsoleApplication1
{

    class Person
    {
        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }
    }

    public class Program
    {
        public static void Main()
        {
            Person[] persons1 = new Person[] { new Person() { Name = "Ahmed", Age = 20 }, new Person() { Name = "Ali", Age = 40 } };
            Person[] persons2 = new Person[] { new Person() { Name = "Zaid", Age = 21 }, new Person() { Name = "Hussain", Age = 22 } };
            Person[] persons3 = new Person[] { new Person() { Name = "Linda", Age = 19 }, new Person() { Name = "Souad", Age = 60 } };

            Person[][] personArrays = new Person[][] { persons1, persons2, persons3 };

            foreach(Person person in MergeOrderedLists<Person, int>(personArrays, person => person.Age))
            {
                Console.WriteLine("{0} {1}", person.Name, person.Age);
            }

            Console.ReadLine();
        }

        static IEnumerable<T> MergeOrderedLists<T, TOrder>(IEnumerable<IEnumerable<T>> orderedlists, Func<T, TOrder> orderBy)
        {
            List<IEnumerator<T>> enumeratorsWithData = orderedlists.Select(enumerable => enumerable.GetEnumerator())
                                                                   .Where(enumerator => enumerator.MoveNext()).ToList();

            while (enumeratorsWithData.Count > 0)
            {
                IEnumerator<T> minEnumerator = enumeratorsWithData[0];
                for (int i = 1; i < enumeratorsWithData.Count; i++)
                    if (((IComparable<TOrder>)orderBy(minEnumerator.Current)).CompareTo(orderBy(enumeratorsWithData[i].Current)) >= 0)
                        minEnumerator = enumeratorsWithData[i];

                yield return minEnumerator.Current;

                if (!minEnumerator.MoveNext())
                    enumeratorsWithData.Remove(minEnumerator);
            }             
        }
    }   
}

Ответ 15

Я подозрительный LINQ достаточно умен, чтобы воспользоваться преимуществом существующего порядка сортировки:

IEnumerable<string> BiggerSortedList =  BigListOne.Union(BigListTwo).OrderBy(s => s);