字符串数字排序

15

可能是重复问题:
C#中的自然排序顺序

我有一个包含许多数字的列表。 但由于一些额外的字母,它们保存为字符串。

我的列表看起来像这样:

1
10
11
11a
11b
12
2
20
21a
21c
A1
A2
...

但它应该看起来像这样

1
2
10
11a
11b
...
A1
A2
...

如何对我的列表进行排序以获得这个结果?


将“number”分解成组件,然后按照它进行排序。 - leppie
2
是的,自然排序是您想要的。正如Jon所说,这是一个重复的问题。在http://zootfroot.blogspot.com.au/2009/09/natural-sort-compare-with-linq-orderby.html上有一篇很好的文章介绍了它。 - Jason Jong
C# 中的自然排序 - L.B
我确认@Jason Jong提出的解决方案运行良好,而下面接受的解决方案未按预期排序。 - user12805184
4个回答

22

根据之前的评论,我会实现一个自定义的IComparer<T>类。从我所了解的情况来看,这些项的结构要么是一个数字,要么是一个由数字后跟字母(s)组成的组合。如果是这种情况,以下IComparer<T>实现应该可以工作。

public class CustomComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        var regex = new Regex("^(d+)");

        // run the regex on both strings
        var xRegexResult = regex.Match(x);
        var yRegexResult = regex.Match(y);

        // check if they are both numbers
        if (xRegexResult.Success && yRegexResult.Success)
        {
            return int.Parse(xRegexResult.Groups[1].Value).CompareTo(int.Parse(yRegexResult.Groups[1].Value));
        }

        // otherwise return as string comparison
        return x.CompareTo(y);
    }
}

使用这个 IComparer<T>,你可以通过以下方式对字符串列表进行排序

var myComparer = new CustomComparer();
myListOfStrings.Sort(myComparer);

以下内容已经测试通过:

2, 1, 4d, 4e, 4c, 4a, 4b, A1, 20, B2, A2, a3, 5, 6, 4f, 1a

测试结果如下:

1, 1a, 2, 20, 4a, 4b, 4c, 4d, 4e, 4f, 5, 6, A1, A2, a3, B2


这很酷,但它不能正确处理版本号,如1.5.2、1.9.9、1.10.17。奇怪的是,Windows资源管理器可以正确排序这些内容...但显然比较器不可用于任何其他C#代码中,以便在按文件名排序时复制与文件资源管理器显示相同的文件名顺序。令人沮丧。 - pmbAustin
3
我认为正则表达式应该是 ^(\\d+)。更好的做法是,将正则表达式改为 (\\d+),这样可以处理 Region 1, Region 10, Region 21, 10, 2 这些情况。 - stack247

10

由于这个算法涉及到许多字符串操作、正则表达式等,我认为它不是一种高效的算法,但它似乎能够工作。

List<string> list1 = new List<string>() { "11c22", "1", "10", "11", "11a", "11b", "12", "2", "20", "21a", "21c", "A1", "A2" };
List<string> list2 = new List<string>() { "File (5).txt", "File (1).txt", "File (10).txt", "File (100).txt", "File (2).txt" };
var sortedList1 = NaturalSort(list1).ToArray();
var sortedList2 = NaturalSort(list2).ToArray();

public static IEnumerable<string> NaturalSort(IEnumerable<string> list)
{
    int maxLen = list.Select(s => s.Length).Max();
    Func<string, char> PaddingChar = s => char.IsDigit(s[0]) ? ' ' : char.MaxValue;

    return list
            .Select(s =>
                new
                {
                    OrgStr = s,
                    SortStr = Regex.Replace(s, @"(\d+)|(\D+)", m => m.Value.PadLeft(maxLen, PaddingChar(m.Value)))
                })
            .OrderBy(x => x.SortStr)
            .Select(x => x.OrgStr);
}

希望能看到一个真正处理版本号的版本...其中有多个数字需要保持顺序:2.5.7、10.3.2、2.18.3等。 - pmbAustin
2
这个很棒!谢谢你! - Tobias Koller
当我提出类似的问题时,看到了这个帖子并尝试了这种方法,结果完美无缺。 - Neil
这个很棒! - ice thailand

4

您需要从每个字符串中提取数字,然后根据数字列表作为键对字符串列表进行排序。 这需要分为两步。

要从每个字符串中提取数字,我认为最简单的方法是使用正则表达式-查找(\d+)的匹配项(如果有负数或小数,您将不得不使用不同的正则表达式)。 假设您在名为ExtractNumber的函数中执行了此操作

现在可以使用一些创意LINQ进行排序,例如:

strings.Select(s=>new { key=ExtractNumber(s), value=s }) // Create a key-value pair
       .OrderBy(p=>p.key)                                // Sort by key
       .Select(p=>p.Value);                              // Extract the values

1
这看起来是使用LINQ的一种优雅的解决方案,但当迭代到A1A2时会发生什么? - Richard
int.Parse((new Regex(@"(?<=pdf_)\d*?(?=.bmp$)")).Match(file).Value); // 匹配 pdf_123456890.bmp - user2102611

0

我对C#还比较新,但是这里有一个我在Java中很欣赏的解决方案:你需要分两步进行,首先定义一个自定义的IComparer,然后在调用sort方法时使用它。所以你应该能够做到类似这样的事情:

public class MyListSorter : IComparer<MyObject>
{
  public int Compare(MyObject obj1, MyObject obj2)
  {
    if ( !Char.IsNumber(obj1) && Char.IsNumber(obj2) )
    {
       return 0;
    }
    else if ( Char.IsNumber(obj1) && !Char.IsNumber(obj2) )
    {
      return 1;
    }
    else
    {
      return obj2.CompareTo(obj1);
    }
  }
}

然后

myObjectList.Sort(new MyListSorter());

有关 IComparer 的更多信息:http://support.microsoft.com/kb/320727


Char.IsNumber(obj1) 怎么可能编译通过呢? - MakePeaceGreatAgain

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