Я только что начал использовать дженерики, и у меня сейчас проблема с сортировкой по нескольким полям.
Корпус:
У меня есть PeopleList как TObjectList<TPerson>
, и я хочу иметь возможность выполнять сортировку в стиле Excel, выбирая по одному полю сортировки за раз, но сохраняя предыдущую сортировку в максимально возможной степени.
EDIT: должна быть возможность изменить последовательность сортировки полей во время выполнения. (Т.е. в одном сценарии пользователь хочет порядок сортировки 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.
Можно ли делать то, что я хочу, используя несколько 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;
Наконец, чтобы использовать критерии сортировки:
(это только для примера, так как логика создания порядка сортировки действительно зависит от приложения):
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;