Mark
Mark

Reputation: 140

Sorting TObjectList<T> swaps equal values

I have the following (simplified) class definition:

  TMyObject = class
  private
    FDoubleValue: Double;
    FText: string;
  protected
  public
    constructor Create(ADoubleValue: Double; AText: string);
    property DoubleValue: Double read FDoubleValue write FDoubleValue;
    property Text: string read FText write FText;
  end;

The following sample code, shows how I am sorting the TObjectList<TMyObject> (FMyObjects) and displaying them in a TListBox.

constructor TfrmMain.Create(AOwner: TComponent);
begin
  inherited;
  FMyObjects := TObjectList<TMyObject>.Create;
  FMyObjects.OwnsObjects := true; // Default but for clarity
end;

destructor TfrmMain.Destroy;
begin
  FMyObjects.Free;
  inherited;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
  ii: Integer;
begin
  FMyObjects.Add(TMyObject.Create(100.00, 'Item 1'));
  FMyObjects.Add(TMyObject.Create(200.00, 'Item 2'));
  FMyObjects.Add(TMyObject.Create(300.00, 'Item 3')); // Duplicate sort value
  FMyObjects.Add(TMyObject.Create(300.00, 'Item 4')); // Duplicate sort value
  FMyObjects.Add(TMyObject.Create(400.00, 'Item 5'));
  ObjectsToListBox;
end;

procedure TfrmMain.SortList;
var
  Comparer: IComparer<TMyObject>;
begin
  Comparer := TDelegatedComparer<TMyObject>.Create(
    function(const MyObject1, MyObject2: TMyObject): Integer
    begin
      result := CompareValue(MyObject1.DoubleValue, MyObject2.DoubleValue, 0);
    end);
  FMyObjects.Sort(Comparer);
end;

procedure TfrmMain.ObjectsToListBox;
var
  ii: Integer;
begin
  ListBox1.Items.Clear;
  for ii := 0 to FMyObjects.Count - 1 do
    ListBox1.Items.Add(Format('%d - %.1f - %s', [ii, FMyObjects[ii].DoubleValue, 
       FMyObjects[ii].Text]));
end;

procedure TfrmMain.Button1Click(Sender: TObject);
begin
  SortList;
  ObjectsToListBox;
end;

Every time Button1 is clicked (and the list sorted), FMyObjects[2] (Item3) and FMyObjects[3] ('Item4') swap position in the list. In my "real world" (drawing) application this is undesirable.

I also experimented with different values for Epsilon in the CompareValue function call and also a different implementation of the anonymous function (comparing values and returning 1, -1 or 0), but neither seems to make a difference.

Am I missing something (e.g. a property that controls this behavior) or is this "by design" and it cannot be prevented?

Upvotes: 2

Views: 167

Answers (1)

Uwe Raabe
Uwe Raabe

Reputation: 47704

This is by design. The internally used Quicksort implementation is not a stable one, so reorder of equal items is expected. To make the sort stable you need to extend your comparer to take that into account. F.i. you can compare the Text properties when the DoubleValue properties are equal.

Upvotes: 2

Related Questions