I have just started to use generics, and I am currently having a problem doing sorting on multiple fields.
Case:
I have a PeopleList as a TObjectList
Your problem is that you are performing two separate sorts. You need to perform a single sort and use what is known as a lexical ordering. You need to use a comparer that compares the primary field and then, only if the primary key compares equal, goes on to compare the secondary key. Like this:
Result := CompareStr(Left.Name, Right.Name);
if Result=0 then
Result := Left.Age-Right.Age;
This approach can be extended to cater for an arbitrary number of keys.
In your update to the question you add the requirement that the key precedence will be determined at runtime. You can do this with a comparison function like this:
function TMyClass.Comparison(const Left, Right: TPerson): Integer;
var
i: Integer;
begin
for i := low(FSortField) to high(FSortField) do begin
Result := CompareField(Left, Right, FSortField[i]);
if Result<>0 then begin
exit;
end;
end;
end;
Here FSortField
is an array containing identifiers for the fields, in descending order of precendence. So FSortField[0]
identifies the primary key, FSortField[1]
identifies the secondary key and so on. The CompareField
function compares the field identified by its third parameter.
So the CompareField
function might be like this:
function CompareField(const Left, Right: TPerson; Field: TField): Integer;
begin
case Field of
fldName:
Result := CompareStr(Left.Name, Right.Name);
fldAge:
Result := Left.Age-Right.Age;
//etc.
end;
end;
If you have a stable sorting algorithm, then you can apply each comparer in reverse order, and the result will be a list sorted in the order you desire. Delphi's list classes use quick sort, which is not a stable sort. You'd need to apply your own sorting routine instead of the built-in ones.
Put your sort criteria in a list that includes the direction to sort and the function to use to compare items. A record like this could help:
type
TSortCriterion<T> = record
Ascending: Boolean;
Comparer: IComparer<T>;
end;
As the user configures the desired ordering, populate the list with instances of that record.
var
SortCriteria: TList<TSortCriterion>;
The Comparer
member will refer to the functions you've already written for comparing based on name and age. Now write a single comparison function that refers to that list. Something like this:
function Compare(const A, B: TPerson): Integer;
var
Criterion: TSortCriterion<TPerson>;
begin
for Criterion in SortCriteria do begin
Result := Criterion.Comparer.Compare(A, B);
if not Criterion.Ascending then
Result := -Result;
if Result <> 0 then
Exit;
end;
end;