Как я могу получить TStringList для сортировки по-разному в Delphi
У меня есть простой TStringList. Я делаю TStringList.Sort на нем.
Затем я замечаю, что подчеркивание "_" сортируется перед заглавной буквой "A". Это было в отличие от стороннего пакета, который сортировал тот же текст и сортировал _ после A.
В соответствии с набором символов ANSI, AZ - это символы 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 ответа
Определите "правильно".
Сортировка i18n полностью зависит от вашей локали.
Так что я полностью согласен с PA., что это не ошибка: стандартное поведение Sort работает так, как предусмотрено, чтобы i18n работал правильно.
Как упоминает Gerry Coll, TStringList.Sort использует AnsiCompareStr и AnsiCompareText (я объясню в нескольких строках, как это происходит).
Но: TStringList является гибким, он содержит Sort, CustomSort и CompareStrings, которые являются виртуальными (так что вы можете переопределить их в классе-потомке)
Кроме того, когда вы вызываете CustomSort, вы можете подключить свою собственную функцию сравнения.
На этот ответ есть функция сравнения, которая делает то, что вы хотите:
- С учетом регистра
- Не используя какую-либо локаль
- Просто сравните порядковый номер символов строк
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;
Который затем использует базовую функцию 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 не закрыта, скорее наоборот: часто это действительно гибкая архитектура.
Часто нужно просто немного покопаться, чтобы увидеть, где можно воспользоваться такой гибкостью.
--jeroen
AnsiCompareStr / AnsiCompareText учитывает больше, чем число символов. Они учитывают языковые стандарты пользователей, поэтому "e" будет сортироваться вместе с "é", "ê" и т. Д.
Чтобы отсортировать его в порядке Ascii, используйте пользовательскую функцию сравнения, как описано здесь.
AnsiCompareStr (CompareString with LOCALE_USER_DEFAULT) имеет ошибку, потому что она получает символы с пунктировкой как равные:
e1 é1 e2 é2
Правильный порядок (например, для чешского):
e1 e2 é1 é2
Кто-нибудь знает, как избежать этой ошибки при оформлении заказа?
11.2.2010: Я должен извиниться, описанное поведение полностью соответствует лингвистическим правилам. Хотя я думаю, что это глупо и "плохо", это не ошибка в функции API.
Проводник в Windows XP использует так называемый интуитивно понятный порядок имен файлов, который дает лучшие результаты, но его нельзя использовать программно.