How do I sort a generic list using a custom comparer?
Solution 1
The Sort
overload you should be using is this one:
procedure Sort(const AComparer: IComparer<TMyRecord>);
Now, you can create an IComparer<TMyRecord>
by calling TComparer<TMyRecord>.Construct
. Like this:
var
Comparison: TComparison<TMyRecord>;
....
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal-Right.intVal;
end;
List.Sort(TComparer<TMyRecord>.Construct(Comparison));
I've written the Comparison
function as an anonymous method, but you could also use a plain old style non-OOP function, or a method of an object.
One potential problem with your comparison function is that you may suffer from integer overflow. So you could instead use the default integer comparer.
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
end;
It might be expensive to call TComparer<Integer>.Default
repeatedly so you could store it away in a global variable:
var
IntegerComparer: IComparer<Integer>;
....
initialization
IntegerComparer := TComparer<Integer>.Default;
Another option to consider is to pass in the comparer when you create the list. If you only ever sort the list using this ordering then that's more convenient.
List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
And then you can sort the list with
List.Sort;
Solution 2
The concise answer:
uses
.. System.Generics.Defaults // Contains TComparer
myList.Sort(
TComparer<TMyRecord>.Construct(
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal - Right.intVal;
end
)
);
Solution 3
I want to share my solution (based on the input I have gathered here).
It's a standard setup. A filedata class that holds data of a single file in a generic TObjectList. The list has the two private attributes fCurrentSortedColumn and fCurrentSortAscending to control the sort order. The AsString-method is the path and filename combined.
function TFileList.SortByColumn(aColumn: TSortByColums): boolean;
var
Comparison: TComparison<TFileData>;
begin
result := false;
Comparison := nil;
case aColumn of
sbcUnsorted : ;
sbcPathAndName: begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcSize : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<int64>.Default.Compare(Left.Size,Right.Size);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcDate : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TDateTime>.Default.Compare(Left.Date,Right.Date);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcState : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TFileDataTestResults>.Default.Compare(Left.FileDataResult,Right.FileDataResult);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
end;
if assigned(Comparison) then
begin
Sort(TComparer<TFileData>.Construct(Comparison));
// Control the sort order
if fCurrentSortedColumn = aColumn then
fCurrentSortAscending := not fCurrentSortAscending
else begin
fCurrentSortedColumn := aColumn;
fCurrentSortAscending := true;
end;
if not fCurrentSortAscending then
Reverse;
result := true;
end;
end;
p1.e
Updated on September 01, 2020Comments
-
p1.e almost 4 years
I'm kinda a Delphi-newbie and I don't get how the Sort method of a TList of Records is called in order to sort the records by ascending integer value. I have a record like the following:
type TMyRecord = record str1: string; str2: string; intVal: integer; end;
And a generic list of such records:
TListMyRecord = TList<TMyRecord>;
Have tried to find a code-example in the help files and found this one:
MyList.Sort(@CompareNames);
Which I can't use, since it uses classes. So I tried to write my own compare function with a little different parameters:
function CompareIntVal(i1, i2: TMyRecord): Integer; begin Result := i1.intVal - i2.intVal; end;
But the compiler always throws a 'not enough parameters' - error when I call it with
open.Sort(CompareIntVal);
, which seems obvious; so I tried to stay closer to the help file:function SortKB(Item1, Item2: Pointer): Integer; begin Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal; end;
with PMyRecord as
PMyRecord = ^TMyRecord;
I have tried different ways of calling a function, always getting some error...
-
p1.e over 11 yearsThanks verry much! Do I need to include anything in 'uses' besides the
uses Generics.Collections,...
, 'cause I get an 'undeclared' forTComparison
andIComparer
invar Comparison: TComparison<TKanteRecord>; IntegerComparer: IComparer<Integer>;
? -
David Heffernan over 11 yearsYou also need Generics.Defaults. Have you found the RTL source code yet. That would help you.
-
TLama almost 11 years@David, are you sure
TComparer
is a good choice for a code you provided ?TComparer
is meant to be the abstract base class. I'd suggest to useTDelegatedComparer
for your code. -
David Heffernan over 10 years@TLama Yes I am sure of that:
TComparer<T>.Construct(Comparison)
is implemented with a call toTDelegatedComparer<T>.Create(Comparison)
. -
Kromster over 9 yearsYou might want to clean up your code sample a bit. Remove unused variables. Add usage example. Correct the syntax.
-
ByteArts almost 9 yearsThe original question was about sorting a generic list, while this example is using the standard TList (list of pointers), which is a different scenario.
-
SOLID Developper almost 6 years@DavidHeffernan The TList<T> does not have a constructor that accept a TComparer<T> as an input parameter in Delphi 10.2. Could you give compilable examples?
-
David Heffernan almost 6 years@Bitman The code here does compile. It does not pass a
TComparer<T>
to any constructor. -
SOLID Developper almost 6 years@DavidHeffernan Definitely not! I interested in your last "solution" that uses a TList<T>.create( IComparer<T> ) constructor and a parameterless TList<T>.Sort method call.
-
David Heffernan almost 6 years@bitman I'm pretty sure it compiles but I can't check right now
-
David Heffernan almost 6 years@bitman As I thought, the code in the answer compiles. You must have transcribed it incorrectly.
-
SOLID Developper almost 6 years@DavidHeffernan As I wrote. I was interested in the second solution. The section after "Another option...". There is no example for this solution.
-
David Heffernan almost 6 years@bitman yes there is, that's the code I checked, and it compiles just fine. Instead of asking what's wrong with my code you'll need to study yours. You made a mistake somewhere.