如何按照数字样式对字符串数组进行排序?

7
我有一个文件名数组,我想按数字顺序对其进行排序,请给我一个解决方案。 例1: 原始数组:[name99.txt, name98.txt, name100.txt]
排序后的数组:[name98.txt, name99.txt, name100.txt]
(使用字符串排序,排序结果为[name100.txt, name98.txt, name99.txt]例2: 原始数组:[a99.txt, b98.txt, b100.txt]
排序后的数组:[a99.txt, b98.txt, b100.txt]
(使用字符串排序,排序结果为[a99.txt, b100.txt, b99.txt]

4
你尝试过什么?显然默认情况下它会按字母数字顺序排序,但你是否尝试编写自定义比较器呢? - Marc Gravell
你的问题令人困惑,因为你没有解释“你得到了什么”和“你想要得到什么”。什么是“排序数组”,什么是“使用字符串排序进行排序的结果”?清楚地表达你的想法,你会得到好的答案。 - Dan Abramov
4个回答

12
string[] ar = new string[] { "name99.txt", "name98.txt", "name100.txt" };
Array.Sort(ar, (a, b) => int.Parse(Regex.Replace(a, "[^0-9]", "")) - int.Parse(Regex.Replace(b, "[^0-9]", "")));

foreach (var a in ar)
    Console.WriteLine(a);

假定您的文件总是以 name###.txt 的形式命名。如果要进行真正的数字排序,请使用以下更复杂的版本:

public static void NumericalSort(string[] ar)
{
    Regex rgx = new Regex("([^0-9]*)([0-9]+)");
    Array.Sort(ar, (a, b) =>
    {
        var ma = rgx.Matches(a);
        var mb = rgx.Matches(b);
        for (int i = 0; i < ma.Count; ++i)
        {
            int ret = ma[i].Groups[1].Value.CompareTo(mb[i].Groups[1].Value);
            if (ret != 0)
                return ret;

            ret = int.Parse(ma[i].Groups[2].Value) - int.Parse(mb[i].Groups[2].Value);
            if (ret != 0)
                return ret;
        }

        return 0;
    });
}

static void Main(string[] args)
{
    string[] ar = new string[] { "a99.txt", "b98.txt", "b100.txt" };

    NumericalSort(ar);

    foreach (var a in ar)
        Console.WriteLine(a);
}

太棒了,Petar!但是,你觉得我的“示例2”怎么样? - hungbm06
刚刚发布了真正的数字排序实现 - 它将涵盖您的第二个示例。 - Petar Ivanov
只是注意,在生产代码中,我会将其提取到单独的方法(和类,可能)中。它并不是立即显而易见的,它做了什么。 - Dan Abramov
@Dan 有些人就是无法满足。你先抱怨自己无法在 MSDN 上查找信息,现在又在抱怨无法理解 .net 实现。 - David Heffernan
2
@David:是的,我是一个挑剔的人。有时这会导致答案得到改进,这也是StackExchange评论的目的吧。在我看来,完美的答案应该包含一个名为FilenameComparerIComparer实现,并且其中应该包含这段代码。这将证明它既易于维护又稳定。 - Dan Abramov

4

可能有一种管理的方法来实现这一点,但我可能会直接使用 P/invoke 调用 StrCmpLogicalW

[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW(String x, String y);    

如果您使用此函数,而不是自己编写比较函数,您将获得与使用逻辑比较的资源管理器和其他系统组件相同的行为。

但是,请注意,在WinAPI不可访问的环境中(如Windows Phone,Mono或Silverlight),这将无法正常工作,在不同的系统上可能会有所不同,并且应该添加注释以便代码的未来维护者知道为什么要使用P/Invoke进行排序。


1
不错,我不知道有这个调用存在。 - Marc Gravell
2
我不会使用WinAPI进行字符串比较。首先,它将代码绑定到一个非常特定的平台(我甚至没有谈论Mono这里-想想Silverlight,Windows Phone)。其次,我们甚至没有与Windows进行互操作。我理解在Windows Forms中调用WinAPI来处理窗口控件绘制的复杂角落情况,或者做一些真正的低级别的东西。但是拜托,使用WinAPI比较字符串?作为.NET开发人员,我不知道StrCmpLogicalW做什么,即使shlwapi.dll存在于我们想要的所有系统上。 - Dan Abramov
为什么不在编辑答案时添加这些要点呢?这比投反对票更有建设性。 - David Heffernan
@David:你说得对,我应该这样做而不是写评论。我也取消了踩的操作。 - Dan Abramov
我正在寻找类似于Windows资源管理器对字符串(实际上是文件名)排序的功能,因此一开始StrCmpLogicalW看起来很不错,但是随后注意到使用此函数时,无法传递任意区域设置。最重要的是,在MSDN文档中的警告:“注意:此函数的行为以及返回的结果可能会在发布版本之间发生更改。_它不应用于规范化排序应用程序_”(强调我的)。这对我来说是个致命缺陷! - fourpastmidnight
显示剩余2条评论

1

1
字母数字排序是常规操作...我可能错了,但我认为这里使用的术语不正确。 - Marc Gravell

0

当数字块的长度不超过9位时,我的方法是很好的:

private string[] NumericalSort(IEnumerable<string> list)
{
    var ar = list.ToArray();

    Array.Sort(ar, (a, b) =>
    {
        var aa = Regex.Replace(a, @"\d+", m => m.Value.PadLeft(9, '0'));
        var bb = Regex.Replace(b, @"\d+", m => m.Value.PadLeft(9, '0'));
        
        return string.Compare(aa, bb);
    });

    return ar;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接