【问题标题】:Unexpected behaviour when sorting a List<String>排序 List<String> 时的意外行为
【发布时间】:2017-06-09 08:39:17
【问题描述】:

如果我尝试按以下方式对字符串列表进行排序:

List<String> lstStrings = new List<string>();

String s1 = "KÜHLSCHRANK";
String s2 = "KUHLSCHRANK";
int i = s1.CompareTo(s2);   // returns 1
int j = s2.CompareTo(s1);   // return -1 

i = StringComparer.InvariantCulture.Compare(s1, s2); // returns 1
j = StringComparer.InvariantCulture.Compare(s2, s1); // returns -1

lstStrings.Add("KÜHLSCHRANK1");
lstStrings.Add("KUTTER");
lstStrings.Add("KUHLSCHRANK2");
lstStrings.Add("KÜHLSCHRANK3");

var lstStrings1 = lstStrings.OrderBy(y => y).ToList();
var lstStrings2 = lstStrings.OrderBy(y => y, StringComparer.InvariantCulture).ToList();
var lstStrings3 = lstStrings.OrderBy(y => y, StringComparer.CurrentCulture).ToList();
var lstStrings4 = lstStrings.OrderBy(y => y, StringComparer.Ordinal).ToList();

我在 lstStrings1lstStrings2lstStrings3 中得到以下结果:

    [0] "KÜHLSCHRANK1"
    [1] "KUHLSCHRANK2"
    [2] "KÜHLSCHRANK3"
    [3] "KUTTER"

只有我的 lstStrings4 显示了我预期的结果:

    [0] "KUHLSCHRANK2"  
    [1] "KUTTER"    
    [2] "KÜHLSCHRANK1"
    [3] "KÜHLSCHRANK3"

谁能解释一下为什么德语的“Ü”默认情况下像普通的“U”一样穿线?

为什么使用 StringComparer.InvariantCulture 的 OrderBy 不关心 StringComparer.InvariantCulture.Compare(s1, s2) 的结果(这意味着 List 像我之前示例中的 lstStrings4 一样有序)?

有没有办法改变这种“默认行为”?

添加: 如果我将数字附加到字符串,比较方法的结果会发生变化:

        String s1 = "KÜHLSCHRANK1";
        String s2 = "KUHLSCHRANK2";
        int i = s1.CompareTo(s2);   // returns -1
        int j = s2.CompareTo(s1);   // return 1 

        i = StringComparer.InvariantCulture.Compare(s1, s2); // returns -1
        j = StringComparer.InvariantCulture.Compare(s2, s1); // returns 1

所以我什至不明白,为什么我的第一个没有数字的测试在每次比较时都不会返回零...

第二次添加: 在 SQL Server 上:

DECLARE @tableDE TABLE (strName NVARCHAR(MAX) COLLATE German_PhoneBook_CI_AI)

INSERT INTO @tableDE (strName)
SELECT e FROM (VALUES('KUHLSCHRANK1'), ('KÜHLSCHRANK2')) f(e)

SELECT * FROM @tableDE ORDER BY strName

给出结果:

KÜHLSCHRANK2
KUHLSCHRANK1

结果: 如果我在 LinqToSql 中执行 OrderBy 并将结果放入列表中, 在 List 变量上添加一个新的OrderBy,即使使用相同的参数,也会改变元素的顺序。

【问题讨论】:

  • 您的初始比较并不是非常有用,因为这些字符串永远不会出现在排序列表中。我建议您尝试比较“KÜHLSCHRANK1”和“KUHLSCHRANK2”。
  • @JonSkeet:添加了请求的示例
  • 对,所以所有的排序部分现在完全不相关,应该删除。
  • (我怀疑答案是“元音变音只在抢七中相关”或类似的)
  • 此处总结的总体字符串比较(添加了德国文化的比较):dotnetfiddle.net/Vg9xL2。检查结果是否正确。

标签: c# list linq cultureinfo


【解决方案1】:

我可以回答为什么序数会为您提供“预期”的结果。根据StringComparer.Ordinal的文档

Ordinal 属性返回的 StringComparer 执行与语言无关的简单字节比较。这在比较以编程方式生成的字符串或比较区分大小写的资源(如密码)时最为合适。

所以U0x55Ü0x220。因此,这可以对您的不同 U 进行排序。但这有一个问题,假设您添加了单词KËTTER(可能不是实际的德语单词,但它是出于演示目的)。您的列表将按如下方式组织:

[0] "KUHLSCHRANK2"
[1] "KUTTER"
[2] "KËTTER"
[3] "KÜHLSCHRANK1"
[4] "KÜHLSCHRANK3"

如您所见,Ë 位于两个不同的 U 之间,这是因为 Ë 的 unicode 为 0x203,而 55&lt;203&lt;220 表示 U&lt;Ë&lt;Ü

因此,如果您的目标是按字母排序,然后按字母的重音排序,我建议您不要使用 Ordinal。

现在,我不能离开 cmets,但您确定要按字母组织,然后按字母的重音排序。我没有看到字典对重音字母和正常对应的字母有区别,这可能就是为什么依赖文化的排序不能给出你需要的结果。

补充说: 我在您的测试中添加了几个案例,所以现在未排序的完整列表看起来有点像这样

[0] "KÜHLSCHRANK1"
[1] "KUHLSCHRANK1"
[2] "KUTTER"
[3] "KUHLSCHRANK2"
[4] "KÜHLSCHRANK2"
[5] "KÜHLSCHRANK3"
[6] "KËTTER"

invariantCulture 和 current Culture 产生相同的结果,即:

[0] "KËTTER"
[1] "KUHLSCHRANK1"
[2] "KÜHLSCHRANK1"
[3] "KUHLSCHRANK2"
[4] "KÜHLSCHRANK2"
[5] "KÜHLSCHRANK3"
[6] "KUTTER"

因此,这表明只有在完全匹配时(忽略重音),重音才会发挥作用。无口音优先。

第二次添加:

根据*

Ü 或 ü 是一个字符,通常表示闭前圆角元音 [y]。在几个扩展的拉丁字母(包括阿塞拜疆语、爱沙尼亚语、匈牙利语和土耳其语)中,它被归类为一个单独的字母,但在加泰罗尼亚语、法语、加利西亚语、德语、奥克西唐语和西班牙语等其他字母中,它被归类为带有元音变音/分音符号的字母 U。

所以在德语中,元音变音不是一个单独的字母,而只是一个口音,如果你改用土耳其文化,它会被视为一个单独的字母。 所以当文化是土耳其语时的结果是:

[0] "KËTTER"
[1] "KUHLSCHRANK1"
[2] "KUHLSCHRANK2"
[3] "KUTTER"
[4] "KÜHLSCHRANK1"
[5] "KÜHLSCHRANK2"
[6] "KÜHLSCHRANK3"

这会产生我相信你想要的结果。只是这将是错误的文化。

回复评论:

正如您所指出的,电话簿确实可以按照您的意愿进行组织,经过一番挖掘,.net 有两种排序算法用于德语。 Documentation 当使用电话簿排序算法时,它会产生结果:

[0] "KËTTER"
[1] "KÜHLSCHRANK1"
[2] "KÜHLSCHRANK2"
[3] "KÜHLSCHRANK3"
[4] "KUHLSCHRANK1"
[5] "KUHLSCHRANK2"
[6] "KUTTER"

为了使用电话簿排序算法,请使用以下代码:

var germanPhone=new CultureInfo(0x00010407);
StringComparer germanPhoneICComp = StringComparer.Create(germanPhone, true);\\set to false if caps are important to you
var lstStrings7 = lstStrings.OrderBy(y => y, germanPhoneICComp).ToList();

【讨论】:

  • 不能同意*的文章。例如,刚刚在 SQLServer 上进行了测试。 Collat​​ion German_PhoneBook_100_CI_AI 将其作为单独的字母进行威胁。
  • 嘿,我更新了我的答案以显示如何像德国电话簿一样进行排序。我很抱歉花了这么长时间。
  • 感谢您的帮助
最近更新 更多