How to make an Excel-Like Sort By A, Then By B in a TObjectList<> using multiple comparers

前端 未结 3 1089
离开以前
离开以前 2021-02-01 20:23

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

相关标签:
3条回答
  • 2021-02-01 20:44

    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;
    
    0 讨论(0)
  • 2021-02-01 20:53

    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.

    0 讨论(0)
  • 2021-02-01 20:54

    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;
    
    0 讨论(0)
提交回复
热议问题