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

微笑、不失礼 提交于 2019-12-02 19:09:11

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;

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!