【问题标题】:How do I sort a generic list using a custom comparer?如何使用自定义比较器对通用列表进行排序?
【发布时间】:2012-11-06 13:35:01
【问题描述】:

我有点像 Delphi 新手,我不明白如何调用 TList of Records 的 Sort 方法以按升序整数值对记录进行排序。 我有如下记录:

 type
   TMyRecord = record
     str1: string;
     str2: string;
     intVal: integer;
   end;

以及此类记录的通用列表:

TListMyRecord = TList<TMyRecord>;

试图在帮助文件中找到一个代码示例并找到了这个:

MyList.Sort(@CompareNames);

我不能使用,因为它使用类。所以我尝试用一​​些不同的参数编写自己的比较函数:

function CompareIntVal(i1, i2: TMyRecord): Integer;
begin
  Result := i1.intVal - i2.intVal;
end;

但是当我用open.Sort(CompareIntVal); 调用它时,编译器总是抛出一个“参数不足”的错误,这似乎很明显;所以我尽量靠近帮助文件:

function SortKB(Item1, Item2: Pointer): Integer;
begin
  Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal;
end;

PMyRecord 为PMyRecord = ^TMyRecord;

我尝试了不同的调用函数的方法,总是出错...

【问题讨论】:

    标签: delphi sorting


    【解决方案1】:

    您应该使用的Sort 重载是这个:

    procedure Sort(const AComparer: IComparer<TMyRecord>);
    

    现在,您可以通过调用 TComparer&lt;TMyRecord&gt;.Construct 创建一个 IComparer&lt;TMyRecord&gt;。像这样:

    var
      Comparison: TComparison<TMyRecord>;
    ....
    Comparison := 
      function(const Left, Right: TMyRecord): Integer
      begin
        Result := Left.intVal-Right.intVal;
      end;
    List.Sort(TComparer<TMyRecord>.Construct(Comparison));
    

    我已将Comparison 函数编写为匿名方法,但您也可以使用普通的老式非 OOP 函数或对象的方法。

    您的比较函数的一个潜在问题是您可能会遇到整数溢出。所以你可以改用默认的整数比较器。

    Comparison := 
      function(const Left, Right: TMyRecord): Integer
      begin
        Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
      end;
    

    重复调用TComparer&lt;Integer&gt;.Default 可能会很昂贵,因此您可以将其存储在全局变量中:

    var
      IntegerComparer: IComparer<Integer>;
    ....
    initialization
      IntegerComparer := TComparer<Integer>.Default;
    

    另一个要考虑的选项是在创建列表时传入比较器。如果您只使用此顺序对列表进行排序,那会更方便。

    List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
    

    然后你就可以对列表进行排序了

    List.Sort;
    

    【讨论】:

    • 非常感谢!除了uses Generics.Collections,... 之外,我是否需要在“用途”中包含任何内容,因为我在var Comparison: TComparison&lt;TKanteRecord&gt;; IntegerComparer: IComparer&lt;Integer&gt;; 中得到TComparisonIComparer 的“未声明”?
    • 您还需要Generics.Defaults。你找到 RTL 源代码了吗?这会对你有所帮助。
    • @David,你确定TComparer 是你提供的代码的好选择吗? TComparer 是抽象基类。我建议您使用TDelegatedComparer 作为您的代码。
    • @TLama 是的,我很确定:TComparer&lt;T&gt;.Construct(Comparison) 是通过调用 TDelegatedComparer&lt;T&gt;.Create(Comparison) 来实现的。
    • @DavidHeffernan TList 没有接受 TComparer 作为 Delphi 10.2 中的输入参数的构造函数。你能给出可编译的例子吗?
    【解决方案2】:

    简洁的答案:

    uses
      .. System.Generics.Defaults // Contains TComparer
    
    myList.Sort(
      TComparer<TMyRecord>.Construct(
        function(const Left, Right: TMyRecord): Integer
        begin
          Result := Left.intVal - Right.intVal;
        end
      )
    );
    

    【讨论】:

      【解决方案3】:

      我想分享我的解决方案(基于我在这里收集的输入)。

      这是一个标准设置。一个文件数据类,它在通用 TObjectList 中保存单个文件的数据。该列表具有两个私有属性 fCurrentSortedColumn 和 fCurrentSortAscending 来控制排序顺序。 AsString 方法是路径和文件名的组合。

      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;
      

      【讨论】:

        【解决方案4】:

        我发现了一个更简单的修改排序函数来按字母顺序排列记录的 TList 或非标准的项目列表。

        例子

        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;
        

        以及调用列表排序的函数

        Contacts.Sort(@CompareNames);
        

        【讨论】:

        • 您可能需要稍微清理一下您的代码示例。删除未使用的变量。添加使用示例。更正语法。
        • 最初的问题是关于对一个通用列表进行排序,而这个例子使用的是标准的 TList(指针列表),这是一个不同的场景。
        猜你喜欢
        • 2020-07-09
        • 2019-05-06
        • 2011-07-09
        • 2020-07-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多