44

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...

Kromster
  • 7,181
  • 7
  • 63
  • 111
p1.e
  • 497
  • 1
  • 4
  • 5

4 Answers4

64

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks verry much! Do I need to include anything in 'uses' besides the `uses Generics.Collections,...`, 'cause I get an 'undeclared' for `TComparison`and `IComparer`in `var Comparison: TComparison; IntegerComparer: IComparer;`? – p1.e Nov 06 '12 at 14:05
  • You also need Generics.Defaults. Have you found the RTL source code yet. That would help you. – David Heffernan Nov 06 '12 at 14:13
  • 1
    @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 use `TDelegatedComparer` for your code. – TLama Aug 20 '13 at 10:57
  • 1
    @TLama Yes I am sure of that: `TComparer.Construct(Comparison)` is implemented with a call to `TDelegatedComparer.Create(Comparison)`. – David Heffernan Mar 18 '14 at 11:06
  • @DavidHeffernan The TList does not have a constructor that accept a TComparer as an input parameter in Delphi 10.2. Could you give compilable examples? – SOLID Developper Aug 31 '18 at 13:21
  • @Bitman The code here does compile. It does not pass a `TComparer` to any constructor. – David Heffernan Aug 31 '18 at 13:37
  • @DavidHeffernan Definitely not! I interested in your last "solution" that uses a TList.create( IComparer ) constructor and a parameterless TList.Sort method call. – SOLID Developper Aug 31 '18 at 13:54
  • @bitman I'm pretty sure it compiles but I can't check right now – David Heffernan Aug 31 '18 at 14:19
  • @bitman As I thought, the code in the answer compiles. You must have transcribed it incorrectly. – David Heffernan Aug 31 '18 at 16:17
  • @DavidHeffernan As I wrote. I was interested in the second solution. The section after "Another option...". There is no example for this solution. – SOLID Developper Aug 31 '18 at 17:30
  • @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. – David Heffernan Aug 31 '18 at 18:04
19

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
  )
);
Kromster
  • 7,181
  • 7
  • 63
  • 111
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
2

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;
1

I found a much simpler modified sort function to alphabetize a TList of records or nonstandard list of items.

Example

PList = ^TContact;
    TContact = record             //Record for database of user contact records
      firstname1 : string[20];
      lastname1 : string[20];
       phonemobile : Integer;       //Fields in the database for contact info
      phonehome : Integer;
      street1 : string;
      street2 : string;

 type
    TListSortCompare = function (Item1,
                                Item2: TContact): Integer;
var
  Form1: TForm1;
  Contact : PList;         //declare record database for contacts
  arecord : TContact;
  Contacts : TList;   //List for the Array of Contacts

function CompareNames(i1, i2: TContact): Integer;
begin
   Result := CompareText(i1.lastname1, i2.lastname1) ;
end;

and the function to call to sort your list

Contacts.Sort(@CompareNames);
  • 1
    You might want to clean up your code sample a bit. Remove unused variables. Add usage example. Correct the syntax. – Kromster Nov 19 '14 at 05:44
  • 6
    The original question was about sorting a generic list, while this example is using the standard TList (list of pointers), which is a different scenario. – ByteArts Sep 02 '15 at 17:45