我刚刚开始使用泛型,目前在对多个字段进行排序时遇到问题。
案例:
我有一个 PeopleList 作为 TObjectList
并且我希望能够通过一次选择一个排序字段来创建类似 Excel 的排序功能,但尽可能保留先前的排序可能的。
编辑:必须可以在运行时更改字段排序顺序。 (即,在一种情况下,用户想要排序顺序 A、B、C - 在另一种情况下,他想要 B、A、C - 在另一个 A、C、D 中)
假设我们有一个未排序的人员列表:
Lastname Age
---------------------
Smith 26
Jones 26
Jones 24
Lincoln 34
现在如果我按姓氏排序:
Lastname ▲ Age
---------------------
Jones 26
Jones 24
Lincoln 34
Smith 26
那么如果我按年龄排序,我想要这样:
Lastname ▲ Age ▲
---------------------
Jones 24
Jones 26
Smith 26
Lincoln 34
为了做到这一点,我制作了两个比较器 - 一个 TLastNameComparer 和一个 TAgeComparer。
我现在调用
PeopleList.Sort(LastNameComparer)
PeopleList.Sort(AgeComparer)
Now,我的问题是这不会产生我想要的输出,但
Lastname ? Age ?
---------------------
Jones 24
Smith 26
Jones 26
Lincoln 34
Smith,26 出现在 Jones,26 之前。所以看起来它没有保留以前的排序。
我知道我可以只创建一个比较器来比较 LastName 和 Age - 但问题是,我必须为 TPerson 中存在的每个字段组合创建比较器。
是否可以使用多个 TComparer 来做我想做的事情,或者我怎样才能完成我想要的事情?
新年更新
仅供未来访客参考,这(几乎)是我现在使用的代码。
首先,我创建了一个基类 TSortCriterion
和一个 TSortCriteriaComparer
,以便将来能够在多个类中使用它们。
我已将 Criterion 和列表分别更改为 TObject
和 TObjectList
,因为我发现如果对象列表自动处理 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;
最后,为了使用排序标准:
(这只是为了示例,因为创建排序顺序的逻辑实际上取决于应用程序):
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;
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;
发布评论
评论(3)
将排序标准放入列表中,其中包括排序方向和用于比较项目的函数。像这样的记录可能会有所帮助:
当用户配置所需的顺序时,用该记录的实例填充列表。
Comparer
成员将引用您已经编写的用于根据姓名和年龄进行比较的函数。现在编写一个引用该列表的比较函数。像这样的事情: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:
As the user configures the desired ordering, populate the list with instances of that record.
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:你的问题是你正在执行两种不同的排序。您需要执行一次排序并使用所谓的词汇排序。您需要使用比较器来比较主字段,然后,仅当主键比较相等时,才继续比较辅助键。像这样:
这种方法可以扩展以适应任意数量的键。
在对问题的更新中,您添加了在运行时确定键优先级的要求。您可以使用如下比较函数来完成此操作:
这里
FSortField
是一个包含字段标识符的数组,按优先级降序排列。因此,FSortField[0]
标识主键,FSortField[1]
标识辅助键,依此类推。CompareField
函数比较由其第三个参数标识的字段。所以
CompareField
函数可能是这样的: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:
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:
Here
FSortField
is an array containing identifiers for the fields, in descending order of precendence. SoFSortField[0]
identifies the primary key,FSortField[1]
identifies the secondary key and so on. TheCompareField
function compares the field identified by its third parameter.So the
CompareField
function might be like this:如果您有稳定排序算法,那么您可以按相反顺序应用每个比较器,结果将是按照您想要的顺序排序的列表。 Delphi的列表类使用快速排序,这不是一种稳定的排序。您需要应用自己的排序例程而不是内置的排序例程。
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.