6

我想对可以包含不同数字类型(双精度、浮点等)的数组进行排序。此代码引发 System.ArgumentException(“值不是 System.Single”)错误:

new dynamic[] { 5L, 4D, 3F, 2U, 1M, 0UL }.ToList().Sort();

我知道我可以使用 LINQ 来做到这一点:

new dynamic[] { 5L, 4D, 3F, 2U, 1M, 0UL }.ToList().OrderBy(x => (decimal)x).ToArray();

但是有没有一种更快的方法,不涉及任何必须强制转换每个元素?

附录:我还希望能够处理列表中的空值,因此即使 LINQ 查询的强制转换也无济于事。

4

1 回答 1

5

首先,删除 ToList()。此操作不是必需的,因此只需将其删除。这将加快事情一点点。

其余的:不,没有更快的方法。如果首先发布一个答案,代码显示性能提高了 2 倍。但是,当我运行比具有更大数组的代码时,它变得相对较慢,甚至比原始代码还要慢。

为什么?整个 OrderBy 分为两部分:键的生成和通过比较这些键对值进行排序。如果数组中的项目数增加,键的生成会线性增长,但比较操作的数量会呈指数增长。

我的代码不需要将所有值转换为小数(密钥生成期间的速度增加),但在排序过程中需要取消装箱的值是性能下降。随着更大的数组,比较操作的数量呈指数级增长,因此拆箱的数量增加了,最终成为了沙子。

我尝试过其他解决方案,例如创建接受两种类型的数字的委托,并在该委托中使用两种类型的最佳比较解决方案的动态编译代码,这总是涉及有时必须转换数字。在分拣过程中,这变成了杀戮。

所以简单地说:不,你的例程不能更快。key-generation-fase 多少时间无关紧要,只要 compare-fase 尽可能快。

对于感兴趣的人,我之前的答案中的原始代码(使用更大的数组不会更快):

    static private dynamic[] testSet = new dynamic[] { 5L, 4D, 3F, null, 2U, 1M, null, 0UL };

    static void Main(string[] args)
    {
        Stopwatch st1 = new Stopwatch();
        st1.Start();
        for(int i = 0; i < 100000; i++)
            Test1();
        st1.Stop();

        Stopwatch st2 = new Stopwatch();
        st2.Start();
        for(int i = 0; i < 100000; i++)
            Test2();
        st2.Stop();
    }

    static public void Test1()
    {
        var result = testSet.OrderBy(x => x == null ? (decimal?)null : (decimal)x).ToArray();
    }

    static public void Test2()
    {
        var result = testSet.OrderBy(x => (object)x, new HeterogeneousNumbersComparer()).ToArray();
    }

    public class HeterogeneousNumbersComparer : IComparer<object>
    {
        public int Compare(object a, object b)
        {
            if (a == null)
                if (b == null)
                    return 0;
                else
                    return -1;
            else if (b == null)
                return +1;

            if (a.GetType() == b.GetType())
            {
                switch(Type.GetTypeCode(a.GetType()))
                {
                    case TypeCode.Byte:
                        return ((Byte)a).CompareTo((Byte)b);
                    case TypeCode.Decimal:
                        return ((Decimal)a).CompareTo((Decimal)b);
                    case TypeCode.Double:
                        return ((Double)a).CompareTo((Double)b);
                    case TypeCode.Int16:
                        return ((Int16)a).CompareTo((Int16)b);
                    case TypeCode.Int32:
                        return ((Int32)a).CompareTo((Int32)b);
                    case TypeCode.Int64:
                        return ((Int64)a).CompareTo((Int64)b);
                    case TypeCode.SByte:
                        return ((SByte)a).CompareTo((SByte)b);
                    case TypeCode.Single:
                        return ((Single)a).CompareTo((Single)b);
                    case TypeCode.UInt16:
                        return ((UInt16)a).CompareTo((UInt16)b);
                    case TypeCode.UInt32:
                        return ((UInt32)a).CompareTo((UInt32)b);
                    case TypeCode.UInt64:
                        return ((UInt64)a).CompareTo((UInt64)b);
                }
            }
            return Convert.ToDecimal(a).CompareTo(Convert.ToDecimal(b));
        }
    }
}

数字(在我的机器上是):
Test1:550ms
Test2:263ms
所以......因子2!

于 2013-04-27T13:30:46.177 回答