pio pio
pio pio

Reputation: 794

To sort TObjectList<T> with multiple criteria

Looking into the example How to make an Excel-Like Sort By A, Then By B in a TObjectList<> using multiple comparers I have built the following test

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.Generics.Defaults,
  System.Generics.Collections,
  System.Contnrs,
  System.SysUtils;

type
  TPerson = class(TObject)
    age: Integer;
    name: string;
  public
    constructor Create(aAge: Integer; aName: string); reintroduce;
  end;

  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;

  TPersonAgeComparer = class(TComparer<TPerson>)
  public
    function Compare(const Left, Right: TPerson): Integer; override;
  end;

  TPersonLastNameComparer = class(TComparer<TPerson>)
  public
    function Compare(const Left, Right: TPerson): Integer; override;
  end;

{ 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;



{ TPersonAgeComparer }

function TPersonAgeComparer.Compare(const Left, Right: TPerson): Integer;
begin
  if Left.age > Right.age then
    Result := -1
  else if Left.age < Right.age then
    result := 1
  else
    result := 0;
end;

{ TPersonLastNameComparer }

function TPersonLastNameComparer.Compare(const Left, Right: TPerson): Integer;
begin
  if Left.name > Right.name then
    Result := -1
  else if Left.name < Right.name then
    result := 1
  else
    result := 0;
end;

{ TPerson }

constructor TPerson.Create(aAge: Integer; aName: string);
begin
  Age := aAge;
  name := aName;
end;

var
  PersonComparer: TSortCriteriaComparer<TPerson>;
  Criterion: TSortCriterion<TPerson>;
  PeopleList: TObjectList<TPerson>;
  Person: 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 := TObjectList<TPerson>.Create;
    PeopleList.Sort(PersonComparer);

    Person := TPerson.Create(26, 'Smith');
    PeopleList.Add(Person);

    Person := TPerson.Create(26, 'Jones');
    PeopleList.Add(Person);

    Person := TPerson.Create(24, 'Jones');
    PeopleList.Add(Person);

    Person := TPerson.Create(34, 'Lincoln');
    PeopleList.Add(Person);

  finally
    PersonComparer.Free;
  end;
end.

but instead of giving the following outcome

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

I have this one

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

I tried to change TPersonLastNameComparer.Comparer and TPersonAgeComparer.Comparer but I cannot get the desided outcome.

What am I doing wrong ?

Upvotes: 0

Views: 483

Answers (1)

pio pio
pio pio

Reputation: 794

Thanks to the input from David I was able to fix this. Here the correct code:

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.Generics.Defaults,
  System.Generics.Collections,
  System.Contnrs,
  System.SysUtils;

type
  TPerson = class(TObject)
    age: Integer;
    name: string;
  public
    constructor Create(aAge: Integer; aName: string); reintroduce;
  end;

  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;

  TPersonAgeComparer = class(TComparer<TPerson>)
  public
    function Compare(const Left, Right: TPerson): Integer; override;
  end;

  TPersonLastNameComparer = class(TComparer<TPerson>)
  public
    function Compare(const Left, Right: TPerson): Integer; override;
  end;

{ 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;



{ TPersonAgeComparer }

function TPersonAgeComparer.Compare(const Left, Right: TPerson): Integer;
begin
  if Left.age > Right.age then
    Result := 1
  else if Left.age < Right.age then
    result := -1
  else
    result := 0;
end;

{ TPersonLastNameComparer }

function TPersonLastNameComparer.Compare(const Left, Right: TPerson): Integer;
begin
  if Left.name > Right.name then
    Result := 1
  else if Left.name < Right.name then
    result := -1
  else
    result := 0;
end;

{ TPerson }

constructor TPerson.Create(aAge: Integer; aName: string);
begin
  Age := aAge;
  name := aName;
end;

var
  PersonComparer: TSortCriteriaComparer<TPerson>;
  Criterion: TSortCriterion<TPerson>;
  PeopleList: TObjectList<TPerson>;
  Person: 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 := TObjectList<TPerson>.Create;


    Person := TPerson.Create(26, 'Smith');
    PeopleList.Add(Person);

    Person := TPerson.Create(26, 'Jones');
    PeopleList.Add(Person);

    Person := TPerson.Create(24, 'Jones');
    PeopleList.Add(Person);

    Person := TPerson.Create(34, 'Lincoln');
    PeopleList.Add(Person);

    PeopleList.Sort(PersonComparer);

  finally
    PersonComparer.Free;
    TradeList.Free;
  end;
end.

Upvotes: 1

Related Questions