如何使一个类似Excel的排序按A,然后按一个TObjectList<>使用多个比较器 [英] How to make an Excel-Like Sort By A, Then By B in a TObjectList<> using multiple comparers

查看:126
本文介绍了如何使一个类似Excel的排序按A,然后按一个TObjectList<>使用多个比较器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚开始使用泛型,我目前在多个字段上进行排序时遇到问题。



案例:
我有一个PeopleList一个TObjectList,我希望能够通过一次选择一个排序字段,然后尽可能多地保留以前的排序,从而形成类似Excel的排序功能。



<编辑:必须在运行时更改字段排序顺序。 (即,在一种情况下,用户想要排序顺序A,B,C - 在另一场景中他想要B,A,C - 在另一个A,C,D)



假设我们有一个未排序的人员列表:

 姓氏年龄
------ ---------------
史密斯26
琼斯26
琼斯24
林肯34

现在如果我按姓氏排序:

 姓氏▲年龄
---------------------
琼斯26
琼斯24
林肯34
史密斯26

然后如果我按Age排序,我想要这样:

 姓氏▲年龄▲
---------------------
琼斯24
琼斯26
史密斯26
林肯34

为了我做了两个比较器 - 一个TLastNameComparer和一个TAgeComparer。



我现在调用

  PeopleList.Sort(LastNameCom parer)
PeopleList.Sort(AgeComparer)

现在我的问题是,这不会产生我想要的输出,但是

 姓氏?年龄? 
---------------------
琼斯24
史密斯26
琼斯26
林肯34

其中Smith,26出现在Jones之前,26代替。所以似乎没有保留以前的排序。



我知道我可以只做一个比较LastName和Age的比较器,但问题是,我必须为TPerson中存在的字段的每个组合进行比较。



是否可以使用多个TComparers做我想要的,或者如何完成我想要的?



谢谢...



新年更新





只是为了参考未来的访客,这是(几乎)我现在使用的代码。



首先我做了一个基类TSortCriterion和一个TSortCriteriaComparer,以便将来可以在多个类中使用这些。
我已将Criterion和列表分别更改为TObject和TObjectList,因为如果objectlist自动处理Criterion的销毁,我发现它更容易。

  TSortCriterion< T> = Class(TObject)
Ascending:Boolean;
比较:IComparer< T>;
结束

TSortCriteriaComparer< T> = Class(TComparer< T>)
私人
SortCriteria:TObjectList< TSortCriterion< T>
Public
构造函数创建;
破坏者破坏;覆盖;
函数比较(Const Right,Left:T):整数;覆盖;
过程ClearCriteria;虚拟;
过程AddCriterion(NewCriterion:TSortCriterion< T);虚拟;
结束;

实现

{TSortCriteriaComparer< T> }

procedure TSortCriteriaComparer< T> .AddCriterion(NewCriterion:TSortCriterion< T);
begin
SortCriteria.Add(NewCriterion);
结束

procedure TSortCriteriaComparer< T> .ClearCriteria;
begin
SortCriteria.Clear;
结束

函数TSortCriteriaComparer< T> .Compare(Const Right,Left:T):Integer;
var
条件:TSortCriterion< T>;
begin
在SortCriteria中的条件开始
结果:= Criterion.Comparer.Compare(Right,Left);
如果不是Criterion.Ascending then
结果:= -Result;
如果Result<> 0然后
退出;
结束
结束

构造函数TSortCriteriaComparer< T> .Create;
开始
继承;
SortCriteria:= TObjectList< TSortCriterion< T> .Create(True);
结束

析构函数TSortCriteriaComparer&T; .Destroy;
begin
SortCriteria.Free;
继承;
结束

最后,为了使用排序条件:
(这只是为了这个缘故的例子,因为创建排序顺序的逻辑真的取决于应用程序):

 过程TForm1.SortList; 
Var
PersonComparer:TSortCriteriaComparer< TPerson> ;;
条件:TSortCriterion< TPerson> ;;
开始
PersonComparer:= TSortCriteriaComparer< TPerson> .Create;
尝试
标准:= TSortCriterion< TPerson>。创建;
Criterion.Ascending:= True;
Criterion.Comparer:= TPersonAgeComparer.Create
PersonComparer.AddCriterion(Criterion);
条件:= TSortCriterion< TPerson>。创建;
Criterion.Ascending:= True;
Criterion.Comparer:= TPersonLastNameComparer.Create
PersonComparer.AddCriterion(Criterion);
PeopleList.Sort(PersonComparer);
//用有序列表的人做某事。
最后
PersonComparer.Free;
结束;
结束;


解决方案

将排序标准放在包含方向的列表中排序和使用功能来比较项目。这样的记录可以帮助:

  type 
TSortCriterion< T> = record
Ascending:Boolean;
比较:IComparer< T>;
结束

当用户配置所需的排序时,使用该记录的实例填充列表。

  var 
SortCriteria:TList< TSortCriterion> ;;

成员将参考功能您已经根据名称和年龄进行比较。现在写一个引用该列表的单一比较函数。这样的东西:

 函数比较(const A,B:TPerson):整数; 
var
条件:TSortCriterion< TPerson> ;;
begin
在SortCriteria中的条件开始
结果:= Criterion.Comparer.Compare(A,B);
如果不是Criterion.Ascending then
结果:= -Result;
如果Result<> 0然后
退出;
结束
结束


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 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 ?

Thanks...

New Years Update

Happy new year to all of you.

Just for reference to future visitors, this is (almost) the code i am using now.

First I made a base class TSortCriterion and a TSortCriteriaComparer 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;

解决方案

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;

这篇关于如何使一个类似Excel的排序按A,然后按一个TObjectList&lt;&gt;使用多个比较器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆