如何在不解析的情况下对数字字符串列表进行排序?

时间:2017-06-23 16:58:48

标签: c# list linq sorting c#-4.0

我有一个高值(大于int 32)的字符串列表,如何在不解析的情况下按升序对它们进行排序?

List = {"4852154879","2652154879","9852154879","1952154879","0652154879"}

我尝试解析如下,但在没有解析

的情况下寻找替代方案和更好的方法
Sorted List = List.OrderBy(x => long.Parse(x.serialNumber)).ToList();

5 个答案:

答案 0 :(得分:10)

首先,我几乎肯定不会采取以下方法。我要么将输入转换为List<long>,要么只使用你已经得到的代码,至少在我完全证明它不够好之前。

然而,由于这是一个非常有趣的问题,让我们尝试编写一个快速IComparer<T>。这取决于:

  • 没有负值
  • 可能的零填充,但没有其他填充
  • 假设所有值都有效

比较两个值时,如果值的长度相同,我们可以使用序数字符串比较。否则:

  • 找出每个字符串中前导零的数量,以计算每个
  • 的“逻辑”长度
  • 如果得到的逻辑长度不同,则以较长者为准
  • 否则,比较前导零之后的字符串

这设法在没有对象分配的情况下执行每次比较。

像(完全未经测试)的东西:

public sealed class NumericComparer : IComparer<string>
{
    public static readonly IComparer<string> Instance { get; } = new NumericComparer();

    private NumericComparer() {}

    public int Compare(string x, string y)
    {
        if (x.Length == y.Length)
        {
            return string.Compare(x, y, StringComparison.Ordinal);
        }
        int xIndex = FindFirstNonZeroIndex(x);
        int yIndex = FindFirstNonZeroIndex(y);
        int lengthComparison = (x.Length - xIndex).CompareTo(y.Length - yIndex);
        if (lengthComparison != 0)
        {
            return lengthComparison;
        }
        return string.Compare(x, xIndex, y, yIndex, x.Length, StringComparison.Ordinal);
    }

    private static int FindFirstNonZeroIndex(string text)
    {
        for (int i = 0; i < text.Length; i++)
        {
            if (text[i] != '0')
            {
                return i;
            }
        }
        // All zeroes? Return text.Length - 1, so that we treat this as
        // "0".
        return text.Length - 1;
    }
}

然后,您可以使用以下内容对列表进行排序:

list.Sort(NumericComparer.Instance);

现在我刚刚对此进行基准测试......就我所知,它看起来与解析大致相同。实际上非常差 - 但比填充形式要好得多。

基准代码:

using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

public class Program
{
    private readonly List<string> list;

    public Program()
    {
        list = Enumerable.Range(0, 100000)
            .Select(_ => GenerateValue())
            .ToList();
    }

    // Just to test the impact of copying...
    [Benchmark]
    public List<string> NoSorting()
    {
        var copy = new List<string>(list);
        return copy;
    }

    [Benchmark]
    public List<string> NoParsing()
    {
        var copy = new List<string>(list);
        copy.Sort(NumericComparer.Instance);
        return copy;
    }

    [Benchmark]
    public List<string> WithParsing() => list.OrderBy(x => long.Parse(x)).ToList();

    static void Main(string[] args)
    {
        BenchmarkRunner.Run<Program>();
    }

    [Benchmark]
    public List<string> WithPadding()
    {
        int maxLength = list.Max(y => y.Length);
        return list.OrderBy(x => x.PadLeft(maxLength, '0')).ToList();
    }        

    // Use the same seed on all tests
    static readonly Random random = new Random(1);
    static string GenerateValue()
    {
        // Up to 11 digits...
        long leading = random.Next(100000);
        long trailing = random.Next(1000000);
        long value = leading * 1000000 + trailing;
        // Pad to 9, 10 or 11 randomly
        int width = random.Next(3) + 9;
        return value.ToString().PadLeft(width, '0');
    }
}
// NumericComparer as per post

结果:

      Method |         Mean |        Error |      StdDev |
------------ |-------------:|-------------:|------------:|
   NoSorting |     473.3 us |     9.359 us |    25.62 us |
   NoParsing |  46,684.7 us |   932.466 us | 1,366.80 us |
 WithParsing |  43,149.8 us |   790.116 us |   700.42 us |
 WithPadding | 275,843.4 us | 3,083.376 us | 2,733.33 us |

替代想法,这绝对更简单:

  • 如果字符串长度相同,则按顺序进行比较
  • 否则,只需解析两个

(我还没有基准测试。)

答案 1 :(得分:2)

查看最低有效数字基数排序:https://en.wikipedia.org/wiki/Radix_sort#Least_significant_digit_radix_sorts

此方法不需要任何解析,它将正确排序字符串,因为数字的字符串值的顺序正确。它也不需要任何填充,即使这确实不是问题:)

但请注意,您必须自己编写代码,如果您不关心效率,使用Hatchet / Mong Zhu的解决方案会更简单。

答案 2 :(得分:2)

正如hatchet已在此处建议的那样是使用0方法的填充

List<string> temp = new List<string> { "4852154879", "2652154879", "9852154879", "1952154879", "0652154879" };
int maxLength = temp.Max(y=>y.Length);
temp = temp.OrderBy(x=>x.PadLeft(maxLength, '0')).ToList();

免责声明:如果数字不是纯intlong而是浮点数(如评论中所指出),这种方法当然会失败。

编辑:

正如阴影所示,由于答案的发展,我将补充这一点:

通过此更改,您可以将Jon的基准测试速度提高约5%:

temp.OrderBy(x => (x.Length==maxLength) ? x : x.PadLeft(maxLength, '0')).ToList(); 

答案 3 :(得分:-3)

我猜您应该使用List<long>代替List<string>

List<long> list = new List<long>(){4852154879,2652154879,9852154879,1952154879,0652154879};
            list = list.OrderBy(x => x).ToList();

答案 4 :(得分:-8)

将它们排序为字符串,它将起作用。排序striungs做字母表,但也正确排序数字