Delphi 如何在对象列表中按A、B进行类似Excel的排序<&燃气轮机;使用多个比较器
我刚刚开始使用泛型,目前在对多个字段进行排序时遇到问题 案例:Delphi 如何在对象列表中按A、B进行类似Excel的排序<&燃气轮机;使用多个比较器,delphi,generics,sorting,tobjectlist,Delphi,Generics,Sorting,Tobjectlist,我刚刚开始使用泛型,目前在对多个字段进行排序时遇到问题 案例: 我有一个PeopleList作为TObjectList,我想通过一次选择一个排序字段,但尽可能保留以前的排序,来实现类似Excel的排序功能 编辑:必须能够在运行时更改字段排序顺序。(即,在一个场景中,用户需要排序顺序A、B、C-在另一个场景中,他需要B、A、C-在另一个A、C、D中) 假设我们有一份未分类的人员名单: Lastname Age --------------------- Smith 26 Jo
我有一个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)
现在我的问题是,这不会产生我想要的输出,但是
Lastname ? Age ?
---------------------
Jones 24
Smith 26
Jones 26
Lincoln 34
26岁的史密斯出现在26岁的琼斯面前。所以它似乎没有保留以前的排序
我知道我只能制作一个比较LastName和Age的比较器,但问题是,我必须为TPerson中存在的每个字段组合制作比较器
是否可以使用多个t比较程序来实现我的目标,或者如何实现我的目标
新年更新
仅供将来的访问者参考,这是(几乎)我现在使用的代码
首先,我创建了一个基类tsortcriteria
和一个TSortCriteriaComparer
,以便将来能够在多个类中使用它们。
我已将标准和列表分别更改为TObject
和TObjectList
,因为我发现如果objectlist自动处理标准的销毁,则更容易
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;
tsortcriteria=Class(TObject)
升序:布尔;
比较器:IComparer;
结束;
TSortCriteriaComparer=类(TComparer)
私有的
分类标准:TObjectList;
公开的
构造函数创建;
毁灭者毁灭;推翻
函数比较(Const Right,Left:T):整数;推翻
程序标准;事实上的
程序添加标准(新标准:TSORTcriteria);事实上的
结束;
实施
{TSortCriteriaComparer}
程序TSortCriteriaComparer.addCriteria(新标准:TsortCriteria);
开始
添加(新标准);
结束;
程序TSortCriteriaComparer.ClearCriteria;
开始
SortCriteria.Clear;
结束;
函数TSortCriteriaComparer.Compare(Const Right,Left:T):整数;
变量
标准:TSORT标准;
开始
对于SortCriteria中的标准,请开始
结果:=标准.比较器.比较(右,左);
如果不是标准,那么上升
结果:=-结果;
如果结果为0,则
出口
结束;
结束;
构造函数TSortCriteriaComparer.Create;
开始
继承;
SortCriteria:=TObjectList.Create(True);
结束;
析构函数TSortCriteriaComparer.Destroy;
开始
免费;
继承;
结束;
最后,为了使用排序标准:
(这只是为了示例,因为创建排序顺序的逻辑实际上取决于应用程序):
程序TForm1.SortList;
变量
个人比较:标准比较器;
标准:TSORT标准;
开始
PersonComparer:=TSortCriteriaComparer.Create;
尝试
条件:=tsortcriteria.Create;
标准。升序:=真;
criteria.Comparer:=TPersonAgeComparer.Create
人员比较。添加标准(标准);
条件:=tsortcriteria.Create;
标准。升序:=真;
criteria.Comparer:=TPersonLastNameComparer.Create
人员比较。添加标准(标准);
PeopleList.Sort(PersonComparer);
//对有序的人员列表进行处理。
最后
个人比较。免费;
结束;
结束;
您的问题是您正在执行两种不同的排序。您需要执行单个排序并使用所谓的词法排序。您需要使用比较主字段的比较器,然后,仅当主键比较相等时,才继续比较次键。像这样:
Result := CompareStr(Left.Name, Right.Name);
if Result=0 then
Result := Left.Age-Right.Age;
这种方法可以扩展到满足任意数量的密钥
在问题的更新中,您添加了将在运行时确定密钥优先级的要求。您可以使用如下比较函数来完成此操作:
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;
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;
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;
如果您有一个稳定的排序算法,那么您可以按相反的顺序应用每个比较器,结果将是一个按所需顺序排序的列表。Delphi的列表类使用快速排序,这不是一种稳定的排序。您需要应用自己的排序例程,而不是内置的排序例程。将您的排序条件放入一个列表中,其中包括排序方向和用于比较项目的函数。这样的记录可能会有所帮助:
type
TSortCriterion<T> = record
Ascending: Boolean;
Comparer: IComparer<T>;
end;
谢谢你的快速回答,大卫。问题是,我希望用户能够在运行时更改排序顺序,通过使用这种方法,我必须为每个字段组合创建一个比较器,例如,如果用户在某个时间希望先按年龄排序,然后按姓氏排序,在另一种情况下,他希望按姓氏排序,然后是年龄-然后我需要两个比较器来做这个。想象一下,如果还可以使用字段排序序列的任意组合按FirstName、PhoneNo、ShoeSize等进行排序,那么我需要多少个比较器。@Techno您只需要一个比较器和一点间接寻址。请看我的更新。这就是我需要的提示,Robs完美抛光的版本使它变得完美。谢谢你的时间帮助。看,应该会给你一些想法。+1这是一个很好的说明,我喜欢在一个很好的封装记录中包含升序/降序。@Rob:这正是我所需要的,正如David提到的,升序/降序功能的很好的使用。感谢您的帮助。Delphi是否有稳定的排序算法(无论Delphi的列表类是否使用)
var
SortCriteria: TList<TSortCriterion>;
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;