15

我有一个简单的 TStringList。我对它做了一个 TStringList.Sort 。

然后我注意到下划线“_”排在大写字母“A”之前。这与对相同文本进行排序并在 A 之后排序 _ 的第三方包形成对比。

根据 ANSI 字符集,AZ 是字符 65 - 90,_ 是 95。所以看起来 3rd 方包使用该顺序,而 TStringList.Sort 不是。

我深入研究了 TStringList.Sort 的内容,它使用 AnsiCompareStr(区分大小写)或 AnsiCompareText(不区分大小写)进行排序。我尝试了两种方法,将我的 StringList 的 CaseSensitive 值设置为 true,然后设置为 false。但在这两种情况下,“_”都排在第一位。

我无法想象这是 TStringList 中的一个错误。所以这里一定有其他我没有看到的东西。那可能是什么?

我真正需要知道的是如何让我的 TStringList 进行排序,使其与另一个包的顺序相同。

作为参考,我使用的是 Delphi 2009,并且在我的程序中使用了 Unicode 字符串。


所以这里的最终答案是用你想要的任何东西(例如非 ansi 比较)覆盖 Ansi 比较,如下所示:

type
  TMyStringList = class(TStringList)
  protected
    function CompareStrings(const S1, S2: string): Integer; override;
  end;

function TMyStringList.CompareStrings(const S1, S2: string): Integer;
begin
  if CaseSensitive then
    Result := CompareStr(S1, S2)
  else
    Result := CompareText(S1, S2);
end;
4

3 回答 3

37

定义“正确”。
i18n排序完全取决于您的语言环境。
所以我完全同意PA的观点,这不是一个错误:默认的排序行为按照设计允许 i18n 正常工作。

就像Gerry提到的那样,TStringList.Sort使用AnsiCompareStrAnsiCompareText(我将在几行中解释它是如何做到的)。

但是: TStringList 是灵活的,它包含SortCustomSortCompareStrings,它们都是虚拟的(因此您可以在后代类中覆盖它们)
此外,当您调用CustomSort时,您可以插入自己的Compare函数。

在这个答案中是一个比较函数,它可以满足你的需求:

  • 区分大小写
  • 不使用任何语言环境
  • 只需比较字符串字符的序数值

CustomSort定义如下:

procedure TStringList.CustomSort(Compare: TStringListSortCompare);
begin
  if not Sorted and (FCount > 1) then
  begin
    Changing;
    QuickSort(0, FCount - 1, Compare);
    Changed;
  end;
end;

默认情况下,Sort方法有一个非常简单的实现,传递一个名为StringListCompareStrings的默认比较函数:

procedure TStringList.Sort;
begin
  CustomSort(StringListCompareStrings);
end;

因此,如果您定义自己的TStringListSortCompare兼容的比较方法,那么您可以定义自己的排序。
TStringListSortCompare 被定义为一个全局函数,它采用 TStringList 和两个索引来引用您要比较的项目:

type
  TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;

您可以使用StringListCompareStrings作为实现自己的指南:

function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := List.CompareStrings(List.FList^[Index1].FString,
                                List.FList^[Index2].FString);
end;

因此,默认情况下 TStringList.Sort 遵循 TList.CompareStrings:

function TStringList.CompareStrings(const S1, S2: string): Integer;
begin
  if CaseSensitive then
    Result := AnsiCompareStr(S1, S2)
  else
    Result := AnsiCompareText(S1, S2);
end;

然后使用带有默认用户语言环境 LOCALE_USER_DEFAULT 的底层Windows API 函数CompareString

function AnsiCompareStr(const S1, S2: string): Integer;
begin
  Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1),
    PChar(S2), Length(S2)) - 2;
end;

function AnsiCompareText(const S1, S2: string): Integer;
begin
  Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1),
    Length(S1), PChar(S2), Length(S2)) - 2;
end;

最后是您需要的比较功能。再次限制:

  • 区分大小写
  • 不使用任何语言环境
  • 只需比较字符串字符的序数值

这是代码:

function StringListCompareStringsByOrdinalCharacterValue(List: TStringList; Index1, Index2: Integer): Integer;
var
  First: string;
  Second: string;
begin
  First := List[Index1];
  Second := List[Index2];
  if List.CaseSensitive then
    Result := CompareStr(First, Second)
  else
    Result := CompareText(First, Second);
end;

Delphi 不是封闭的,恰恰相反:它通常是一个非常灵活的架构。
通常只是一点点挖掘,看看你可以在哪里获得这种灵活性。

——杰伦

于 2010-02-01T10:24:27.187 回答
5

AnsiCompareStr / AnsiCompareText 考虑的不仅仅是字符数。他们会考虑用户的区域设置,因此“e”将与“é”、“ê”等一起排序。

要使其按 Ascii 顺序排序,请使用此处所述的自定义比较功能

于 2010-02-01T07:18:36.140 回答
0

AnsiCompareStr (CompareString with LOCALE_USER_DEFAULT) 有错误,因为它获取的字符与标点相同:

e1 é1 e2 é2

正确的顺序是(例如捷克语):

e1 e2 e1 e2

有人知道如何在订购时避免这个错误吗?


11.2.2010:我必须道歉,描述的行为完全符合语言规则。虽然我认为这很愚蠢和“糟糕”,但 API 函数中的错误并不是错误的。

Windows XP 中的资源管理器使用所谓的直观文件名排序,可以提供更好的结果,但不能以编程方式使用。

于 2010-02-10T14:28:21.470 回答