C#最快联合2组排序值

时间:2011-08-23 17:32:52

标签: c# .net union sortedset

合并2组排序值的最快方法是什么?速度(大O)在这里很重要;不清楚 - 假设这已经完成了数百万次。

假设您不知道值的类型或范围,但有一个高效的IComparer<T>和/或IEqualityComparer<T>

给出以下数字:

var la = new int[] { 1, 2, 4, 5, 9 };
var ra = new int[] { 3, 4, 5, 6, 6, 7, 8 };

我期待1,2,3,4,5,6,7,8,9。以下存根可用于测试代码:

static void Main(string[] args)
{
    var la = new int[] { 1, 2, 4, 5, 9 };
    var ra = new int[] { 3, 4, 5, 6, 6, 7, 8 };

    foreach (var item in UnionSorted(la, ra, Int32Comparer.Default))
    {
        Console.Write("{0}, ", item);
    }
    Console.ReadLine();
}

class Int32Comparer : IComparer<Int32>
{
    public static readonly Int32Comparer Default = new Int32Comparer();
    public int Compare(int x, int y)
    {
        if (x < y)
            return -1;
        else if (x > y)
            return 1;
        else
            return 0;
    }
}

static IEnumerable<T> UnionSorted<T>(IEnumerable<T> sortedLeft, IEnumerable<T> sortedRight, IComparer<T> comparer)
{
}

4 个答案:

答案 0 :(得分:3)

以下方法返回正确的结果:

static IEnumerable<T> UnionSorted<T>(IEnumerable<T> sortedLeft, IEnumerable<T> sortedRight, IComparer<T> comparer)
{
    var first = true;

    var continueLeft = true;
    var continueRight = true;

    T left = default(T);
    T right = default(T);

    using (var el = sortedLeft.GetEnumerator())
    using (var er = sortedRight.GetEnumerator())
    {
        // Loop until both enumeration are done.
        while (continueLeft | continueRight)
        {
            // Only if both enumerations have values.
            if (continueLeft & continueRight)
            {
                    // Seed the enumeration.
                    if (first)
                    {
                        continueLeft = el.MoveNext();
                        if (continueLeft)
                        {
                            left = el.Current;
                        }
                        else 
                        {
                            // left is empty, just dump the right enumerable
                            while (er.MoveNext())
                                yield return er.Current;
                            yield break;
                        }

                        continueRight = er.MoveNext();
                        if (continueRight)
                        {
                            right = er.Current;
                        }
                        else
                        {
                            // right is empty, just dump the left enumerable
                            if (continueLeft)
                            {
                                // there was a value when it was read earlier, let's return it before continuing
                                do
                                {
                                    yield return el.Current;
                                }
                                while (el.MoveNext());
                            } // if continueLeft is false, then both enumerable are empty here.
                            yield break;
                        }

                        first = false;
                    }

                // Compare them and decide which to return.
                var comp = comparer.Compare(left, right);
                if (comp < 0)
                {
                    yield return left;
                    // We only advance left until they match.
                    continueLeft = el.MoveNext();
                    if (continueLeft)
                        left = el.Current;
                }
                else if (comp > 0)
                {
                    yield return right;
                    continueRight = er.MoveNext();
                    if (continueRight)
                        right = er.Current;
                }
                else
                {
                    // The both match, so advance both.
                    yield return left;
                    continueLeft = el.MoveNext();
                    if (continueLeft)
                        left = el.Current;
                    continueRight = er.MoveNext();
                    if (continueRight)
                        right = er.Current;
                }
            }
            // One of the lists is done, don't advance it.
            else if (continueLeft)
            {
                yield return left;
                continueLeft = el.MoveNext();
                if (continueLeft)
                    left = el.Current;
            }
            else if (continueRight)
            {
                yield return right;
                continueRight = er.MoveNext();
                if (continueRight)
                    right = er.Current;
            }
        }
    }
}

空间为~O(6),时间为〜(max(n,m))(其中m为第二组)。

答案 1 :(得分:2)

我要给LINQ带来疑问,并说这可能和你没有写过多代码一样快:

var result = la.Union(ra);

EDITED: 谢谢,我错过了排序部分。

你可以这样做:

var result = la.Union(ra).OrderBy(i => i);

答案 2 :(得分:2)

这会使你的UnionSorted功能变得不那么通用,但你可以通过对类型做出假设来做一些改进。如果你在循环内部进行比较(而不是调用Int32Comparer)那么这将节省一些函数调用开销。

所以你的UnionSorted声明变成了这个......

static IEnumerable<int> UnionSorted(IEnumerable<int> sortedLeft, IEnumerable<int> sortedRight)

然后你在循环中执行此操作,摆脱对comparer.Compare() ...

的调用
//var comp = comparer.Compare(left, right); // too slow

int comp = 0;
if (left < right)
    comp = -1;
else if (left > right)
    comp = 1;

在我的测试中,这个速度提高了大约15%。

答案 3 :(得分:0)

我会这样解决问题。 (我正在做一个明显减轻这个问题难度的假设,只是为了说明这个想法。)

假设:集合中包含的所有数字均为非负数。

创建一个至少n位的单词,其中n是您期望的最大值。 (如果您期望的最大值为12,则必须创建一个16位的字。)。

迭代两组。对于每个值,valor val1

完成后,计算设置为1的位数。创建该大小的数组。 逐个浏览每个位,如果设置了n,则将n添加到新数组。