Как я могу получить TStringList для сортировки по-разному в Delphi - PullRequest
13 голосов
/ 01 февраля 2010

У меня есть простой TStringList. Я делаю TStringList.Sort на нем.

Тогда я замечаю, что подчеркивание "_" сортируется перед заглавной буквой "A". Это было в отличие от стороннего пакета, который сортировал тот же текст и сортировал _ после A.

В соответствии с набором символов ANSI, A-Z - это символы 65 - 90, а _ - 95. Таким образом, похоже, что сторонний пакет использует этот порядок, а TStringList.Sort - нет.

Я углубился в интуицию TStringList.Sort, и она сортируется с использованием AnsiCompareStr (с учетом регистра) или AnsiCompareText (без учета регистра). Я попробовал это обоими способами, установив значение CaseSensitive моего StringList в 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;

Ответы [ 3 ]

36 голосов
/ 01 февраля 2010

Определите «правильно».
i18n сортировка полностью зависит от вашей локали.
Поэтому я полностью согласен с PA в том, что это не ошибка: поведение по умолчанию Sort работает так, как предусмотрено, чтобы i18n работал правильно.

Как Джерри упоминает, TStringList.Sort использует AnsiCompareStr и AnsiCompareText (я объясню в нескольких строках, как это происходит что).

Но: TStringList является гибким, он содержит Сортировка , CustomSort и CompareStrings , которые все являются виртуальными (поэтому вы можете переопределить их в классе-потомке)
Кроме того, когда вы вызываете CustomSort , вы можете подключить свою собственную функцию Compare .

В этом ответе есть функция 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 имеет очень простую реализацию, передавая по умолчанию Compare функцию с именем 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;

Который затем использует базовую функцию Windows API CompareString с языком пользователя по умолчанию LOCALE_USER_DEFAULT :

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 не закрыт, скорее наоборот: часто это действительно гибкая архитектура.
Часто нужно немного покопаться, чтобы увидеть, где можно воспользоваться такой гибкостью.

- Йерун

5 голосов
/ 01 февраля 2010

AnsiCompareStr / AnsiCompareText учитывают больше, чем число символов. Они учитывают языковые стандарты пользователей, поэтому «e» будет сортироваться вместе с «é», «ê» и т. Д.

Для сортировки в порядке Ascii используйте пользовательскую функцию сравнения , как описано здесь

0 голосов
/ 10 февраля 2010

AnsiCompareStr (CompareString with LOCALE_USER_DEFAULT) имеет ошибку, потому что он получает символы с пунктировкой как равные:

e1 e1 e2 e2

Правильный заказ (например, для чешского):

e1 e2 e1 e2

Кто-нибудь знает, как избежать этой ошибки при оформлении заказа?


11.2.2010: Я должен извиниться, описанное поведение полностью соответствует лингвистическим правилам. Хотя я думаю, что это глупо и «плохо», это не ошибка в функции API.

Проводник в Windows XP использует так называемое интуитивное упорядочивание имен файлов, которое дает лучшие результаты, но его нельзя использовать программно.

...