Tec*_*boy 19 delphi sorting generics tobjectlist
我刚刚开始使用泛型,我目前在多个字段上进行排序时遇到问题.
案例:
我有一个PeopleList作为a TObjectList<TPerson>,我希望能够通过一次选择一个排序字段来制作类似Excel的排序功能,但尽可能保持先前的排序.
编辑:必须可以在运行时更改字段排序顺序.(即,在一种情况下,用户想要排序顺序A,B,C - 在另一种情况下,他想要B,A,C - 在另一个A,C,D中)
让我们说我们有一个未分类的人员列表:
Lastname Age
---------------------
Smith 26
Jones 26
Jones 24
Lincoln 34
Run Code Online (Sandbox Code Playgroud)
现在,如果我按LastName排序:
Lastname ? Age
---------------------
Jones 26
Jones 24
Lincoln 34
Smith 26
Run Code Online (Sandbox Code Playgroud)
然后如果我按年龄排序,我想要这个:
Lastname ? Age ?
---------------------
Jones 24
Jones 26
Smith 26
Lincoln 34
Run Code Online (Sandbox Code Playgroud)
为了做到这一点,我制作了两个比较器 - 一个TLastNameComparer和一个TAgeComparer.
我现在打电话
PeopleList.Sort(LastNameComparer)
PeopleList.Sort(AgeComparer)
Run Code Online (Sandbox Code Playgroud)
现在我的问题是,这不会产生我想要的输出,但是
Lastname ? Age ?
---------------------
Jones 24
Smith 26
Jones 26
Lincoln 34
Run Code Online (Sandbox Code Playgroud)
史密斯,26岁出现在琼斯之前,26岁.所以看起来它没有保留以前的排序.
我知道我只能制作一个比较LastName和Age的比较器 - 但问题是,我必须为TPerson中存在的每个字段组合制作比较器.
是否可以使用多个TComparers做我想做的事情,或者我如何实现我想要的?
仅供参考未来的访问者,这(几乎)是我现在使用的代码.
首先,我创建了一个基类TSortCriterion<T>,TSortCriteriaComparer<T>以便将来能够在多个类中使用它们.我已经改变了标准和名单TObject,并TObjectList分别,因为我发现它比较容易,如果该链表类自动处理标准的破坏.
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;
Run Code Online (Sandbox Code Playgroud)
最后,为了使用排序标准:(这只是为了示例,因为创建排序顺序的逻辑实际上取决于应用程序):
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;
Run Code Online (Sandbox Code Playgroud)
Rob*_*edy 16
将排序条件放在一个列表中,该列表包括排序方向和用于比较项目的函数.像这样的记录可以帮助:
type
TSortCriterion<T> = record
Ascending: Boolean;
Comparer: IComparer<T>;
end;
Run Code Online (Sandbox Code Playgroud)
当用户配置所需的排序时,使用该记录的实例填充列表.
var
SortCriteria: TList<TSortCriterion>;
Run Code Online (Sandbox Code Playgroud)
该Comparer成员将是指你已经为基于姓名和年龄比较书面的功能.现在写一个引用该列表的比较函数.像这样的东西:
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;
Run Code Online (Sandbox Code Playgroud)
您的问题是您正在执行两种不同的排序.您需要执行单一排序并使用所谓的词法排序.你需要使用比较主字段,然后,只有主键比较平等的,那张比较次要键比较器.像这样:
Result := CompareStr(Left.Name, Right.Name);
if Result=0 then
Result := Left.Age-Right.Age;
Run Code Online (Sandbox Code Playgroud)
可以扩展该方法以满足任意数量的密钥.
在对问题的更新中,添加了在运行时确定键优先级的要求.您可以使用这样的比较函数执行此操作:
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;
Run Code Online (Sandbox Code Playgroud)
这FSortField是一个包含字段标识符的数组,按优先级的降序排列.因此,FSortField[0]标识主键,FSortField[1]标识辅助键等.该CompareField函数比较由其第三个参数标识的字段.
所以CompareField函数可能是这样的:
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;
Run Code Online (Sandbox Code Playgroud)