19

ジェネリックスを使い始めたばかりですが、現在、複数のフィールドで並べ替えを行う際に問題が発生しています。

ケース:
PeopleListをとして持っていて、TObjectList<TPerson>一度に1つの並べ替えフィールドを選択することで、Excelのような並べ替え関数を作成できるようにしたいのですが、以前の並べ替えは可能な限り維持します。

編集:実行時にフィールドの並べ替え順序を変更できる必要があります。(つまり、あるシナリオでは、ユーザーはソート順A、B、Cを必要とします-別のシナリオでは、B、A、Cを必要とします-さらに別のA、C、Dを必要とします)

ソートされていない人のリストがあるとしましょう:

Lastname     Age
---------------------
Smith        26
Jones        26
Jones        24
Lincoln      34

ここで、LastNameで並べ替えると:

Lastname ▲   Age
---------------------
Jones        26
Jones        24
Lincoln      34
Smith        26

次に、年齢で並べ替えると、これが必要になります。

Lastname ▲   Age ▲
---------------------
Jones        24
Jones        26
Smith        26
Lincoln      34

これを行うために、2つの比較子を作成しました。1つはTLastNameComparerで、もう1つはTAgeComparerです。

私は今電話します

PeopleList.Sort(LastNameComparer)
PeopleList.Sort(AgeComparer)

今私の問題は、これが私が望む出力を生成しないということですが、

Lastname ?   Age ?
---------------------
Jones        24
Smith        26
Jones        26
Lincoln      34

代わりに、Smith、26がJones、26の前に表示されます。したがって、以前の並べ替えが保持されていないようです。

LastNameとAgeの両方を比較する比較子を1つだけ作成できることは知っていますが、問題は、TPersonに存在するフィールドの組み合わせごとに比較子を作成する必要があることです。

複数のTComparerを使用してやりたいことを実行することは可能ですか、またはどうすればやりたいことを達成できますか?

新年の更新

将来の訪問者のために、これは(ほぼ)私が現在使用しているコードです。

まず、基本クラス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;
4

3 に答える 3

17

並べ替えの方向と項目の比較に使用する関数を含むリストに、並べ替え条件を入力します。次のような記録が役に立ちます。

type
  TSortCriterion<T> = record
    Ascending: Boolean;
    Comparer: IComparer<T>;
  end;

ユーザーが目的の順序を構成すると、リストにそのレコードのインスタンスが入力されます。

var
  SortCriteria: TList<TSortCriterion>;

メンバーは、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;
于 2011-12-29T21:32:13.653 に答える
6

あなたの問題は、2 つの別々の並べ替えを実行していることです。単一のソートを実行し、字句順序付けと呼ばれるものを使用する必要があります。プライマリ フィールドを比較し、プライマリ キーが等しい場合にのみ、セカンダリ キーを比較する比較子を使用する必要があります。このような:

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;

以下FSortFieldは、フィールドの識別子を含む配列で、優先度の高い順に並べられています。したがってFSortField[0]、主キーをFSortField[1]識別し、副キーを識別します。このCompareField関数は、3 番目のパラメーターで識別されるフィールドを比較します。

したがって、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;
于 2011-12-29T20:08:07.077 に答える
3

安定した並べ替えアルゴリズムがある場合は、各比較子をの順序で適用できます。結果は、希望する順序で並べ替えられたリストになります。Delphi のリスト クラスはクイック ソートを使用しますが、これは安定したソートではありません。組み込みの並べ替えルーチンではなく、独自の並べ替えルーチンを適用する必要があります。

于 2011-12-29T20:58:14.407 に答える