Lazarus, сортировка TStringList.

Преамбула

Иногда список строк (TStringList) требуется сортировать, что, как бы, понятно. Но во Freepascal сортировка устроена довольно странно, в C# у аналогичного класса из коробки несколько больше возможностей, во всяком случае, можно поменять направление, по возрастанию или по убыванию. С хитрыми сортировками, конечно, тоже вылезает нетривиальщина, но на то они и хитрые сортировки.

Тестовые списки

Для начала сформируем тестовые списки, для русского и английского языков. Там код тривиален, потому не буду загружать заметку, ссылки на PasteBin:

Тестовый список для английского языка
Тестовый список для русского языка

Заодно уж и выведем списки в их изначальном виде:

Стандартная сортировка

Она же единственная из коробки, является стандартной сортировкой по возрастанию, т.е. список сортируется от меньшего к большему, а об алгоритме будет ниже. Вызов функции простой:

lstTest.Sort;

Пробуем. Выводим список на экран:

WriteLn('Сортировка по возрастанию (по-умолчанию, логическая):');
i:=0;
while i < lstTest.Count do begin
WriteLn(lstTest[i]);
inc(i);
end;

Writeln(); WriteLn('Press Enter...'); ReadLn();

Результат:


Как обычно, с поддержкой русского язкы все весело, но чинить эту поддержку в данной заметке не буду. Кому сильно надо — пишите.

Стандартный алгоритм сравнения строк во freepascal

Алгоритм:

1. Строки сравниваются посимвольно. Например, в словах parrot и puppy первые символы (p и p) равны, а далее a меньше u, т.е u в кодовой таблице находится ниже, чем a.
2. На этом сравнение прекращается, строка puppy больше чем parrot.
3. Если начальные символы строк совпадают, то в дело вступает длина, чем строка длиннее, тем она больше, поэтому, зависимая от центра Каталония, все еще больше, чем кот, который гуляет сам по себе:

program test;
var
  S1:string;
  S2:string;

begin
  S1:='cat';
  S2:='catalonia';
  if S1 < S2 then Writeln ('S1 (',S1,') < S2 (',S2,')' );
  if S1 = S2 then Writeln ('S1 (',S1,') = S2 (',S2,')' );
  if S1 > S2 then Writeln ('S1 (',S1,') > S2 (',S2,')' );
  Readln();
end.

Вывод:

S1 (cat) < S2 (catalonia)

Что, конечно, печально, потому что кот свободнее Каталонии, и явно больше в этом смысле.

Пользовательские функции сортировки

Ограничение, накладываемое одной функцией сортировки в комплекте класса TStringList легко решается тем, что можно подключить любую пользовательскую сортировку ссылкой на внешнюю функцию.

Для этого надо установить пользовательскую функцию сортировки, таким вот образом:

lstTest.CustomSort(@MySort);

где MySort — имя вашей функции сортировки. Естественно, она должна быть заранее создана, чтобы все откомпилировалось и заработало.

Не забудьте про символ @ перед именем функции, в Delphi не надо было его указывать, компилятор сам знал, где вместо имени функции вставить ссылку на ее адрес, в freepascal это надо указывать явно.

Формат функции следующий:

MySort(List: TStringList; Index1, Index2: Integer): Integer;

Т.е. на входе нужна переменная типа TStringList и две переменные типа Integer, для индексов строк в списке, функция должна возвращать значение типа Integer:

1 — Если строка Index1 > Index2
0 — Если строки равны
-1 — Если строка Index1 < Index2

Конечно, по мнению пользовательского алгоритма сортировки. Например, можем повторить стандартный метод сортировки:

function SortByAsc(List: TStringList; Index1, Index2: Integer): Integer;
begin
  if List[Index1]>List[Index2] then
  begin
    Result := 1;
    Exit;
  end;
  if List[Index1]=List[Index2]
    then Result := 0
    else Result := -1;
end;

Внезапно, починился русский язкы 🙂

Сортировка по убыванию

function SortByDesc(List: TStringList; Index1, Index2: Integer): Integer;
  begin
    if List[Index1]<List[Index2] then
    begin
      Result := 1;
      Exit;
    end;
    if List[Index1]=List[Index2]
      then Result := 0
      else Result := -1;
  end;

Вообще просто, достаточно заменить знак > на < в операторе сравнения.

Другие пользовательские сортировки

Возможности пользовательских сортировок не ограничены практически ничем. Покажу, как обычно, самое простое — сортировка строк по длине.

Сортировка по возрастанию:

function SortByLenAsc(List: TStringList; Index1, Index2: Integer): Integer;
begin
  if Length(List[Index1])>Length(List[Index2]) then
  begin
    Result := 1;
    Exit;
  end;
  if Length(List[Index1])=Length(List[Index2])
    then Result := 0
    else Result := -1;
end;

Т.е. просто применяем функцию Length в операторе сравнения:

Length(List[Index1])

Тот же вариант, но по убыванию:

function SortByLenDesc(List: TStringList; Index1, Index2: Integer): Integer;
begin
  if Length(List[Index1])<Length(List[Index2]) then
  begin
    Result := 1;
    Exit;
  end;
  if Length(List[Index1])=Length(List[Index2])
    then Result := 0
    else Result := -1;
end;

Тестовые программы

Пример для английского языка на GitHub
Пример для русского языка на GitHub

По мотивам

Обсуждения на Исходниках, но более подробнее и лучше у меня.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *