c# - 合并排序的 IEnumerable<T> 的最有效算法

标签 c# linq performance algorithm optimization

我有几个巨大的我想要合并的已排序可枚举序列。这些列表作为 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; }
    }

应该保留重复项,我们不关心它们在新序列中的顺序。您看到我可以使用的任何明显优化了吗?

最佳答案

这是我的第四个(感谢@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 的元组支持:

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
    }
}

关于c# - 合并排序的 IEnumerable<T> 的最有效算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2767007/

相关文章:

c# - 如何使用 OpenXML SDK 2.5 从 word 文档中复制公式?

c# - SSIS 2012 - 第二个脚本执行时变量为空

C# Application Restart 不调用程序 Main()

c# - 扩展 LINQ 表达式

c# - 为多级表达式生成 Expression<Func<TEntity, bool>>

java - 变量性能 - java

performance - 在 julia 中 try catch 或类型转换性能 -(Julia 73 秒,Python 0.5 秒)

c# - 使用 Entity Framework 返回数据表

LINQ 查询优化?

java - 字符串方法 -​​ 数组还是不数组?