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<TPerson>
and I want to be able to make an Excel-like sorting function, by selecting one sort-field at a time, but keeping the previous sorting as much as possible.
EDIT: It must be possible to change the field sort sequence at runtime. (Ie. in one scenario, the user wants the sort order A,B,C - in another scenario he wants B,A,C - in yet another A,C,D)
Lets say we have an unsorted list of people :
Lastname Age
---------------------
Smith 26
Jones 26
Jones 24
Lincoln 34
Now if I sort by LastName :
Lastname ▲ Age
---------------------
Jones 26
Jones 24
Lincoln 34
Smith 26
Then if I sort by Age, I want this :
Lastname ▲ Age ▲
---------------------
Jones 24
Jones 26
Smith 26
Lincoln 34
In order to do this, I have made two Comparers - One TLastNameComparer and one TAgeComparer.
I now call
PeopleList.Sort(LastNameComparer)
PeopleList.Sort(AgeComparer)
Now my problem is that this does not produce the output I want, but
Lastname ? Age ?
---------------------
Jones 24
Smith 26
Jones 26
Lincoln 34
where Smith,26 appears before Jones,26 instead. So it seems like it doesn't keep the previous sorting.
I know that I can make just one comparer that compares both LastName and Age - but the problem is, that I then have to make comparers for each combination of the fields present in TPerson.
Is it possible to do what I want using multiple TComparers or how can I accomplish what I want?
New Years Update
Just for reference to future visitors, this is (almost) the code I am using now.
First I made a base class TSortCriterion<T>
and a TSortCriteriaComparer<T>
in order to be able to use these in multiple classes in the future.
I have changed the Criterion and the list to TObject
and TObjectList
respectively, as I found it easier if the objectlist automatically handles destruction of the Criterion.
TSortCriterion<T> = Class(TObject)
Ascending: Boolean;
Comparer: IComparer<T>;
end;
TSortCriteriaComparer<T> = Class(TComparer<T>)
Private
SortCriteria : TObjectList<TSortCriterion<T>>;
Public
Constructor Create;
Destructor Destroy; Override;
Function Compare(Const Right,Left : T):Integer; Override;
Procedure ClearCriteria; Virtual;
Procedure AddCriterion(NewCriterion : TSortCriterion<T>); Virtual;
End;
implementation
{ TSortCriteriaComparer<T> }
procedure TSortCriteriaComparer<T>.AddCriterion(NewCriterion: TSortCriterion<T>);
begin
SortCriteria.Add(NewCriterion);
end;
procedure TSortCriteriaComparer<T>.ClearCriteria;
begin
SortCriteria.Clear;
end;
function TSortCriteriaComparer<T>.Compare(Const Right, Left: T): Integer;
var
Criterion: TSortCriterion<T>;
begin
for Criterion in SortCriteria do begin
Result := Criterion.Comparer.Compare(Right, Left);
if not Criterion.Ascending then
Result := -Result;
if Result <> 0 then
Exit;
end;
end;
constructor TSortCriteriaComparer<T>.Create;
begin
inherited;
SortCriteria := TObjectList<TSortCriterion<T>>.Create(True);
end;
destructor TSortCriteriaComparer<T>.Destroy;
begin
SortCriteria.Free;
inherited;
end;
Finally, in order to use the sort criteria : (this is just for the sake of the example, as the logic of creating the sort order really depends on the application) :
Procedure TForm1.SortList;
Var
PersonComparer : TSortCriteriaComparer<TPerson>;
Criterion : TSortCriterion<TPerson>;
Begin
PersonComparer := TSortCriteriaComparer<TPerson>.Create;
Try
Criterion:=TSortCriterion<TPerson>.Create;
Criterion.Ascending:=True;
Criterion.Comparer:=TPersonAgeComparer.Create
PersonComparer.AddCriterion(Criterion);
Criterion:=TSortCriterion<TPerson>.Create;
Criterion.Ascending:=True;
Criterion.Comparer:=TPersonLastNameComparer.Create
PersonComparer.AddCriterion(Criterion);
PeopleList.Sort(PersonComparer);
// Do something with the ordered list of people.
Finally
PersonComparer.Free;
End;
End;