В Delphi часто возникает задача сортировки списка записей TList<T> по различным полям записи. В данной статье мы рассмотрим несколько подходов к решению этой задачи, включая использование указателей, компараторов и альтернативные методы.
Постановка задачи:
Имеется список записей типа dmR00, хранящийся в TList<dmR00>. Необходимо реализовать функцию сортировки, которая позволяла бы сортировать список по любому из полей записи (например, ID, x, y, z) без жесткой привязки к конкретному полю в коде сортировки.
type
dmR00 = record
ID : integer;
x, y, z : integer;
end;
dmA00 = TList<dmR00>;
Решение 1: Использование компаратора (рекомендуемый подход)
Наиболее гибким и современным способом решения этой задачи является использование компараторов. Delphi предоставляет интерфейс IComparer<T> и класс TComparer<T>, которые позволяют создавать пользовательские функции сравнения для сортировки.
type
TMyRecord = record
Foo: integer;
Bar: string;
end;
TMyList = TList<TMyRecord>;
TMyField = (mfFoo, mfBar);
procedure SortList(List: TMyList; Field: TMyField);
var
Comparer: IComparer<TMyRecord>;
begin
case Field of
mfFoo:
Comparer := TComparer<TMyRecord>.Construct(
function(const A, B: TMyRecord): integer
begin
Result := (A.Foo - B.Foo);
end);
mfBar:
Comparer := TComparer<TMyRecord>.Construct(
function(const A, B: TMyRecord): integer
begin
Result := CompareText(A.Bar, B.Bar);
end);
else
exit;
end;
List.Sort(Comparer);
end;
// Пример использования:
var
MyList := TList<TMyRecord>.Create;
begin
// ... Заполнение списка ...
SortList(MyList, mfFoo); // Сортировка по полю Foo
SortList(MyList, mfBar); // Сортировка по полю Bar
end;
Преимущества:
Гибкость: Легко добавлять новые поля для сортировки, просто добавляя новые ветки в case оператор.
Типобезопасность: Компилятор проверяет типы данных, что снижает вероятность ошибок.
Чистый код: Код сортировки становится более читаемым и понятным.
Использование стандартных средств: Нет необходимости изобретать велосипед.
Решение 2: Использование указателей (менее предпочтительный, но полезный для понимания)
Хотя использование компараторов является предпочтительным, понимание работы с указателями может быть полезным.
type
TMyRecord = record
Foo: integer;
Bar: string;
end;
PMyRecord = ^TMyRecord;
var
MyList := TList<TMyRecord>.Create;
// Получение указателя на элемент списка:
var
Items := MyList.List; // Получаем доступ к внутреннему динамическому массиву
SomeItem: PMyRecord := @Items[0]; // Получаем указатель на первый элемент
// Работа с элементом по указателю:
SomeItem^.Foo := 42;
SomeItem.Bar := 'Hello world';
Важно! Указатели, полученные таким образом, действительны только до тех пор, пока размер списка не изменяется (добавление или удаление элементов может привести к перераспределению памяти, и указатели станут недействительными).
Использование указателей для сортировки (не рекомендуется):
Использовать указатели непосредственно в алгоритме сортировки крайне не рекомендуется из-за сложности и потенциальных проблем с управлением памятью. Лучше использовать компараторы.
Решение 3: Использование параметра Key (альтернативный подход, но не самый гибкий)
Предложенный автором исходного вопроса вариант с использованием параметра key и case оператора также возможен, но менее гибок и масштабируем, чем использование компараторов.
type
dmR00 = record
ID : integer;//key-0
x,//key-1
y,//key-2
z : integer;//key-3
end;
dmA00 = TList<dmR00>;
function Foo.BubbleSort(AList: dmA00; key:byte; out Error: String): Boolean;
var
jRec, j1Rec: dmR00;
i,j : integer;
begin
try
for i := 0 to AList.Count - 2 do
begin
for j := 0 to AList.Count - 2 - i do
begin
jRec := aList[j];
j1Rec := aList[j+1];
case key of
0: if jRec.ID > j1Rec.ID then Swap(AList[j], AList[j+1]);
1: if jRec.x > j1Rec.x then Swap(AList[j], AList[j+1]);
2: if jRec.y > j1Rec.y then Swap(AList[j], AList[j+1]);
3: if jRec.z > j1Rec.z then Swap(AList[j], AList[j+1]);
end;
end;
end;
Result := True;
except
on E: Exception do
begin
Error := ('Exception class name = ' + E.ClassName + sLineBreak +
'Exception message = ' + E.Message);
Result := False;
end;
end;
end;
Недостатки:
Ограниченная масштабируемость: При добавлении новых полей необходимо добавлять новые ветки в case оператор.
Менее читаемый код:case оператор может стать громоздким при большом количестве полей.
Отсутствие типобезопасности: Компилятор не проверяет, что значение key соответствует существующему полю записи.
Альтернативное решение: Индексированный список (для больших объемов данных)
Если работа ведется с очень большими списками записей и важна производительность, можно рассмотреть вариант с созданием отдельного индексированного списка. В этом случае, данные хранятся в одном списке (например, TList<dmR00>), а индексы (например, TList<Integer>) хранятся в другом списке. Сортировка выполняется над списком индексов, а доступ к данным осуществляется через отсортированный список индексов. Этот метод позволяет избежать копирования больших объемов данных при сортировке.
Заключение:
Для сортировки списка записей TList<T> по произвольному полю рекомендуется использовать компараторы. Этот подход обеспечивает гибкость, типобезопасность и чистый код. Использование указателей для непосредственной сортировки не рекомендуется из-за сложности и потенциальных проблем с управлением памятью. Альтернативные решения, такие как использование параметра key или индексированного списка, могут быть полезны в определенных ситуациях, но имеют свои ограничения. Выбор оптимального решения зависит от конкретных требований задачи и приоритетов (гибкость, производительность, простота кода).
В статье рассматриваются различные подходы к сортировке списка записей в Delphi по произвольному полю, включая использование компараторов, указателей и параметра key, с рекомендацией использовать компараторы для гибкости и типобезопасности.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.