为什么“-less”在排序时排在“hello”之后而不是之前?

6

我使用CaseInsensitiveComparer.DefaultInvariant时发现一些非常奇怪的排序行为。以破折号“ - ”开头的单词最终被排序,好像没有破折号一样,而不是按照其他标点符号一样排在字母前面。

所以,给定 { "hello", ".net", "-less"},结果为{".net", "hello", "-less" },而不是预期的{"-less", ".net", "hello"}。

换言之,作为一个测试案例:

[TestMethod]
public void TestMethod1()
{
    var rg = new String[] { 
        "x", "z", "y", "-less", ".net", "- more", "a", "b"
    };

    Array.Sort(rg, CaseInsensitiveComparer.DefaultInvariant);

    Assert.AreEqual(
        "- more,-less,.net,a,b,x,y,z", 
        String.Join(",", rg)
    );
}

...这种情况下会出现失败:

Assert.AreEqual failed. 
Expected:<- more,-less,.net,a,b,x,y,z>. 
Actual:  <- more,.net,a,b,-less,x,y,z>.

有什么想法是怎么回事吗?

编辑:

看起来,默认情况下,.NET在对字符串进行排序时会进行一些花哨的操作,导致前导连字符被排序到奇怪的位置,使得co-op和coop一起排序。因此,如果您希望您的前导连字符单词以其他标点符号的方式出现在开头,则必须告诉它不要这样做:

Array.Sort(rg, (a, b) => String.CompareOrdinal(a, b));

当没有空格附加时,它可能被视为类似于负号。 - Merlyn Morgan-Graham
如果每个问题都可以表示为单元测试,那该多好啊。 - Bob
我不清楚点“.”,但是看起来StringComparer.InvariantCulture在进行比较之前会忽略所有破折号“-”。 - tsul
4个回答

12
比较过程使用CultureInfo.InvariantCulture来确定排序顺序和大小写规则。根据文化背景不同,字符串比较可能会产生不同的结果。有关特定于文化的比较的更多信息,请参见System.Globalization命名空间和编码与本地化。 从这里开始。 一个单词排序执行一种针对字符串的特定于文化的比较,其中某些非字母数字Unicode字符可能会被分配特殊权重。例如,连字符(-)可能会被分配非常小的权重,以便“coop”和“co-op”在排序列表中相邻出现。 从这里开始。

那么 OP 可以按照以下方式解决混淆吗?“字符串排序也执行区域敏感比较。它类似于单词排序,但没有特殊情况,并且所有非字母数字符号都排在所有字母数字 Unicode 字符之前。可以通过调用具有选项参数的 CompareInfo.Compare 方法重载来使用字符串排序规则比较两个字符串,该参数提供了 CompareOptions.StringSort 的值。请注意,这是 .NET Framework 提供的唯一使用字符串排序规则比较两个字符串的方法。” - Steve Townsend
+1,好答案。思考许多不同类型的破折号会让你的大脑爆炸,键盘上的那个总是错的:http://en.wikipedia.org/wiki/Dash - Hans Passant

3
为了按照您的需求对字符串进行排序,您需要创建一个比较器类,该类使用Compareinfo class比较字符串。该类允许您指定各种比较方法,最适合您需求的是OrdinalIgnoreCase。
来自MSDN:
忽略的搜索值
比较操作(例如IndexOf或LastIndexOf方法执行的操作)可能会产生意外结果,如果要搜索的值被忽略。 如果搜索值是空字符串(""),由于比较选项而不考虑代码点的字符或字符串,或具有没有语言学意义的代码点的值,则忽略搜索值。 例如,如果IndexOf方法的搜索值是空字符串,则返回值为零。
注意: 尽可能使用接受CompareOptions值以指定所需比较类型的字符串比较方法。 一般来说,用户界面比较最好使用语言选项(使用当前区域设置),而安全性比较应该指定Ordinal或OrdinalIgnoreCase。
我修改了您的测试用例,并且这个执行正确:
public class MyComparer:Comparer<string>
{
    private readonly CompareInfo compareInfo;

    public MyComparer()
    {
        compareInfo = CompareInfo.GetCompareInfo(CultureInfo.InvariantCulture.Name);
    }

    public override int Compare(string x, string y)
    {
        return compareInfo.Compare(x, y, CompareOptions.OrdinalIgnoreCase);
    }
}

public class Class1
{
    [Test]
    public void TestMethod1()
    {
        var rg = new String[] { 
    "x", "z", "y", "-less", ".net", "- more", "a", "b"
};

        Array.Sort(rg, new MyComparer());

        Assert.AreEqual(
            "- more,-less,.net,a,b,x,y,z",
            String.Join(",", rg)
        );


    }
}

2
我的猜测是,对于排序目的,紧挨着字母的破折号会被忽略。当你对一系列单词进行排序时,希望“inter-nation”和“international”彼此相邻,不是吗?另一方面,单独的破折号被认为是有意义的。

不完全是-我希望(并期望)根据它们在ASCII字符集中的位置对嵌入的非字母字符进行排序。您是否认为"inter-national"和"international"在这个比较器中是相同的? - Steve Townsend

0

排序顺序取决于文化,因此您不能假设字符将按ASCII顺序排序。

http://msdn.microsoft.com/en-us/library/a7zyyk0c.aspx

在你的例子中,“h”(U+0048)在“dash”(U+2013)之前,因此“hello”将出现在“-less”之前。 “.” (U+002E)在两者之前,所以“.net”会先出现。

错误的解释。尝试对“aa”、“-bb”和“cc”进行排序,你会发现Unicode顺序在这里并不重要。更有可能的是比较器“吃掉了”前导点和破折号。 - tsul

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