如何比较包含非英文字符的 unicode 字符串以按字母顺序排序?

Posted

技术标签:

【中文标题】如何比较包含非英文字符的 unicode 字符串以按字母顺序排序?【英文标题】:How do I compare unicode strings containing non-english characters to sort alpabetically? 【发布时间】:2011-10-26 10:49:15 【问题描述】:

我正在尝试根据其中包含非英语字符的 unicode 字符串值对数组/列表/任何数据进行排序,我希望它们按字母顺序正确排序。

我已经编写了很多代码(D2010,win XP),我认为这些代码对于未来的国际化来说非常可靠,但事实并非如此。它全部使用 unicodestring(字符串)数据类型,到目前为止,我只是将英文字符放入 unicode 字符串中。

看来我必须承认犯了一个非常严重的 unicode 错误。我和我的德国朋友交谈,并尝试了一些德语 ß,(ß 是 'ss',应该在字母表中的 S 和 T 之前)和 ö 等(注意变音符号),我的排序算法都不再起作用了。结果非常混乱。垃圾。

从那时起,我一直在广泛阅读并了解了很多关于 unicode 排序规则的不愉快的事情。事情看起来很严峻,比我想象的要严峻得多,我把这件事搞砸了。我希望我遗漏了一些东西,而事情实际上并不像现在看起来那么严峻。我一直在修补查看 windows api 调用 (RtlCompareUnicodeString) 没有成功(保护错误),我无法让它工作。我了解到的 API 调用的问题是它们在各种较新的 Windows 平台上发生了变化,而且随着 delphi 即将跨平台,后来使用 linux,我的应用程序是客户端服务器,所以我需要关注这一点,但情况是这样是不是(坏的)我会感谢任何前进的进步,即 win api specific.

是否使用win api 函数RtlCompareUnicodeString 来明显的解决方案?如果是这样,我真的应该再试一次,但是我对 unicode 排序规则所涉及的所有问题感到吃惊,而且我根本不清楚我应该怎么做才能以这种方式比较这些字符串。

我了解了 IBM ICU c++ 开源项目,它有一个 delphi 包装器,尽管它适用于旧版本的 ICU。这似乎是一个非常全面的解决方案,它独立于平台。当然,我不能考虑为此创建一个 delphi 包装器(或更新现有的包装器)以获得 unicode collat​​ion 的良好解决方案?

我很高兴听到两个层面的建议:-

A) 一个特定于 Windows 的非便携式解决方案,我现在很高兴,忘记客户端服务器的后果! B) 一个更便携的解决方案,不受各种 XP/vista/win7 unicode api 函数变体的影响,因此让我在 XE2 mac 支持和未来的 linux 支持方面处于有利地位,更不用说客户端服务器的复杂性了。

顺便说一句,我真的不想做'make-do'解决方案,在比较之前扫描字符串并替换某些我读过的棘手字符等。我在上面给出了德语示例,这只是一个示例,我想让它适用于所有(或至少大多数,远东,俄语)语言,我不想为一两种特定语言做变通方法。我也不需要任何关于排序算法的建议,它们很好,只是字符串比较位错了。

我希望我错过/做了一些愚蠢的事情,这一切看起来都很头疼。

谢谢。


编辑,Rudy,这就是我尝试调用 RtlCompareUnicodeString 的方式。很抱歉耽搁了我的时间。

program Project26

$APPTYPE CONSOLE

uses
  SysUtils;


var
  a,b:ansistring;

  k,l:string;
  x,y:widestring;
  r:integer;

procedure RtlInitUnicodeString(
  DestinationString:pstring;
  SourceString:pwidechar) stdcall; external 'NTDLL';

function RtlCompareUnicodeString(
  String1:pstring;
  String2:pstring;
  CaseInSensitive:boolean
  ):integer stdcall; external 'NTDLL';


begin

  x:='wef';
  y:='fsd';

  RtlInitUnicodeString(@k, pwidechar(x));
  RtlInitUnicodeString(@l, pwidechar(y));

  r:=RtlCompareUnicodeString(@k,@l,false);

  writeln(r);
  readln;

end.

我意识到这很可能是错误的,我不习惯直接调用 api 函数,这是我最好的猜测。

关于您的 StringCompareEx api 函数。这看起来非常好,但仅适用于 Vista +,我使用的是 XP。 StringCompare 在 XP 上,但那不是 Unicode!

回顾一下,正在进行的基本任务是比较两个字符串,并根据当前 Windows 语言环境中指定的字符排序顺序进行比较。

谁能确定 ansicomparetext 是否应该这样做?它对我不起作用,但是其他人说应该这样做,而我读过的其他内容表明应该这样做。

这是我在德语语言环境中使用 AnsiCompareText 时得到的 31 个测试字符串(空格分隔 - 没有字符串包含空格):-

arß Asß asß aßs no nö ö ön oo öö oöo öoö öp pö ss SS ßaß ßbß sß Sßa Sßb ßß ssss SSSS ßßß ssßß SSßß ßz ßzß z zzz

编辑 2。

我仍然很想知道我是否应该期望 AnsiCompareText 使用区域设置信息来工作,正如 lkessler 所说的那样,lkessler 之前也发布过关于这些主题的帖子,而且似乎之前已经经历过。

但是,根据 Rudy 的建议,我也一直在检查 CompareStringW - 它与 CompareString 共享相同的文档,因此它不是我之前所说的非 unicode。

即使 AnsiCompareText 不起作用,虽然我认为它应该起作用,但 win32api 函数 CompareStringW 确实应该起作用。现在我已经定义了我的 API 函数,我可以调用它,我得到了一个结果,并且没有错误......但是无论输入字符串如何,我每次都会得到相同的结果!它每次都返回 1 - 这意味着小于。这是我的代码

var
  k,l:string;

function CompareStringW(
  Locale:integer;
  dwCmpFlags:longword;
  lpString1:pstring;
  cchCount1:integer;
  lpString2:pstring;
  cchCount2:integer
  ):integer stdcall; external 'Kernel32.dll';

begin;

  k:='zzz';
  l:='xxx';

  writeln(length(k));
  r:=comparestringw(LOCALE_USER_DEFAULT,0,@k,3,@l,3);

  writeln(r); // result is 1=less than, 2=equal, 3=greater than
  readln;

end;

在经历了很多痛苦之后,我觉得我现在正在取得进展。很高兴知道 AnsiCompareText,以及我在上面的 CompareStringW api 调用中做错了什么。谢谢。


编辑 3

首先,我自己修复了对 CompareStringW 的 api 调用,当我应该执行 PString(mystring) 时,我传入了 @mystring。现在一切正常。

r:=comparestringw(LOCALE_USER_DEFAULT,0,pstring(k),-1,pstring(l),-1);

现在,当我仍然得到与开始时相同的排序结果时,你可以想象我的沮丧......

arß asß aßs Asß no nö ö ön oo öö oöo öoö öp pö ss SS ßaß ßbß sß Sßa Sßb ßß ssss SSSS ßßß ssßß SSßß ßz ßzß z zzz

当我意识到排序顺序是正确的,而且一开始就正确时,你也可以想象我的极度沮丧,更不用说同时高兴了!说起来有点恶心,但一开始就没有任何问题——这完全是因为我缺乏德语知识。我相信排序是错误的,因为您可以看到上面的字符串以 S 开头,然后它们以 ß 开头,然后再次以 s 开头,然后返回 ß 等等。好吧,我不会说德语,但是我仍然可以清楚地看到它们没有正确排序-我的德国朋友告诉我 ß 在 S 之后和 T 之前...我错了!正在发生的事情是字符串函数(AnsiCompareText 和 winapi CompareTextW)都用 'ss' 代替每个 'ß',用正常的 'o' 代替每个 'ö'......所以如果我把上面的结果和搜索并按照我得到的描述替换...

arss asss asss Asss no no o on oo oo ooo ooo op po ss SS ssass ssbss sss Sssa Sssb ssss ssss SSSS ssssss ssssss SSssss ssz sszss z zzz

对我来说看起来很正确!并且一直如此。

我非常感谢您提供的所有建议,非常抱歉像这样浪费您的时间。那些德语ß让我很困惑,内置的delphi函数或其他任何东西都没有错。它看起来就像有。我错误地将它们与测试数据中的正常 's' 组合在一起,任何其他字母都不会产生这种未排序的错觉!波浪形的ß让我看起来像个傻瓜! ßs!

Rudy 和 lkessler 我们都特别乐于助人,你们俩,我不得不接受 lkessler 的回答是最正确的,对不起 Rudy。

【问题讨论】:

+1 表示想要并尝试做正确的事情。 谢谢伊恩。我刚刚花了4天时间搞砸了这个!下次当我发现我的猫在追自己的尾巴时,我不会笑得那么大声...... @csharpdefector:很高兴你能弄明白。并感谢您的详细问题和跟进,这将在其他人遇到相同问题时提供帮助。我在 *** 上的一些问题的答案告诉我,我的理解是错误的,而这种对我的想法的纠正对我来说甚至比简单地得到我的答案更有价值。 *** 的美妙之处在于,当您完全被难住时,您通常会在几天甚至几小时内得到答案。极好的。 (是的,我是一个 SO 助推器) 【参考方案1】:

您说您自己调用 Windows API 调用时遇到问题。您能否发布代码,以便这里的人们可以看到它失败的原因?它并不像看起来那么难,但它确实需要一些小心。 RtlCompareUnicodeStrings() 级别太低的 ISTM。

我找到了一些解决方案:

非便携式

您可以使用 Windows API 函数 CompareStringEx。这将使用 Unicode 特定的排序规则类型进行比较。您可以指定如何完成此操作(请参阅链接)。它确实需要宽字符串,即指向它们的 PWideChar 指针。如果调用时遇到问题,请大声喊叫,我会尝试添加一些演示代码。

或多或少的便携

为了使其或多或少具有可移植性,您可以编写一个比较两个字符串的函数,并使用条件定义为平台选择不同的比较 API。

【讨论】:

这些看起来不错的建议 Rudy,ty。是的,我将发布代码,并在大约 90 分钟后尝试上述方法,我现在必须出去。【参考方案2】:

尝试使用CompareStr 区分大小写,或使用CompareText 区分大小写,如果您希望您的排序在任何语言环境中都完全相同。

如果您希望您的排序特定于用户的语言环境,请使用AnsiCompareStr 区分大小写,或使用AnsiCompareText 不区分大小写。

有关更多信息,请参阅:How can I get TStringList to sort differently in Delphi。

【讨论】:

我相信您对 HeartWare 的评论是正确的。我首先希望 AnsiCompareText 能够工作。我刚刚意识到,因为我的 Windows 语言栏浮动在屏幕上。当我运行我的应用程序时,语言栏突然将用户区域设置恢复为英语,即使我的用户区域设置,我什至更改了我的系统区域设置并重新启动 - 都是德语。我没有会更改任何代码页或语言环境的项目设置。我怀疑这是问题所在。我注意到你在上一个关于国际化的问题中遇到了同样的问题。 V 令人沮丧!用户区域设置不会停留在德国区域设置! 忘记了我的默认设置是英语,只要我启动一个新的应用程序,它就会变成英语 - 哎呀。我现在在德语语言环境中,我的 ß 仍然没有正确排序。【参考方案3】:

在 Unicode 中,字符的数字顺序当然不是排序顺序。 HeartWare 提到的 AnsiCompareText 在比较字符时确实考虑了语言环境的细节,但是,正如您所发现的,它对排序顺序没有任何作用。您要查找的内容称为语言的排序顺序,它指定考虑变音符号等的语言的字母排序顺序。它们在旧的 Ansi 代码页面中有所隐含,但也没有考虑使用相同字符集的语言之间的排序差异。

我查看了 D2010 文档。除了一些 TIB* 组件外,我没有找到任何链接。 C++ builder 似乎确实有一个比较函数,它考虑了排序规则,但这在 Delphi 中用处不大。在那里你可能不得不直接使用一些 Windows 的 API 函数。

文档:

整理整理:http://www.siao2.com/2008/12/06/9181413.aspx 排序规则术语:http://msdn.microsoft.com/en-us/library/ms143726(SQL.90).aspx(虽然这与 MS SQL 2005 相关,但它可能会有所帮助)

“整理“整理”所有内容”一文由 Michael Kaplan 撰写,他对 Unicode 的所有事物以及各种语言的所有复杂性都有非常深入的了解。在从 D2006 移植到 D2009 时,他的博客对我来说非常宝贵。

【讨论】:

【参考方案4】:

你试过 AnsiCompareText 吗?尽管它被称为“Ansi”,但我相信它会调用特定于操作系统的 Unicode 比较例程......

它还应该使您免受跨平台依赖的影响(前提是 Embarcadero 在他们所针对的各种操作系统中提供兼容版本)。

我不知道与各种奇怪的 Unicode 字符串编码方式的比较效果如何,但请尝试一下,让我们知道结果...

【讨论】:

是的,我满怀希望地尝试过,但这并不好。 A-Z 很好,但我的 ß 等排序错误:( 使用 AnsiCompareText,如果您的语言环境是德国,您的 ß 应该正确排序,但如果您的语言环境是其他任何东西,它们可能不会正确排序。

以上是关于如何比较包含非英文字符的 unicode 字符串以按字母顺序排序?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Unicode 字符输出为一对 ASCII 字符?

如何使用 SQLAPI++ 从 SQL 服务器读取 unicode 字符?

比较 unicode 字符时,Javascript 字符串比较失败

忽略特殊 Unicode 字符的字符串比较

如何识别字符串是不是包含 unicode 字符?

如何从python中的unicode字符串中删除除数字和“,”之外的所有字符?