【问题标题】:How to sort a dynamic array of heterogeneous numbers?如何对异构数字的动态数组进行排序?
【发布时间】:2013-04-26 19:34:49
【问题描述】:

我想对可以包含不同数值类型(double、float 等)的数组进行排序。此代码引发 System.ArgumentException(“Value is not a 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 查询的强制转换也无济于事。

【问题讨论】:

  • 最终,要比较两个数字,它们必须是兼容的类型。我不确定你能否绕过这个限制。在每种类型上使用 IComparable.CompareTo(object) 实现在性能方面会更差,因为每次调用都需要对参数进行装箱。
  • 为什么要从一组异构数字开始?
  • Stilgar:我使用 JSON.NET 的动态加载从 JSON 文件加载它们,这似乎将数字转换为双精度数或整数,具体取决于它们是否有小数点。
  • @cdhowie 如果他们在dynamic[] 中,他们已经被装箱...
  • @MarcGravell 好点...不知何故我错过了那个细节。

标签: c#


【解决方案1】:

首先,删除 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));
        }
    }
}

数字(在我的机器上是):
测试1:550ms
测试 2:263 毫秒
所以......因素2!!!

【讨论】:

    猜你喜欢
    • 2013-05-25
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-15
    相关资源
    最近更新 更多