Can I pass in one function for TObjectList.IndexOf, and another function for TObjectList.Sort?
Asked Answered
R

3

1

Summarization:

TList.IndexOf (TList defined in the unit Classes.pas) iterates linearly through the contained items, and compares the reference. TList.IndexOf (TList defined in the unit Generics.Collections.pas) also iterates linearly through the contained items, but uses a comparer to compare whether the items are equal.

Both TList.Sort and TList.Sort can use a comparer.

=================================================

For an instance of the TForceList type defined in the following unit, I could use

instance.Sort(@ForceCompare);

to QuickSort it using its Value field as sorting criteria. However, when I call

instance.IndexOf(AnotherInstance)

I want to use its ElementZ field as comparing criteria, i.e., the ForceEqual function. I am wondering how can I achieve this?

PS: If the generics collection is used, I guess I could use

TList<TForce>.Create(TComparer<TForce>.Construct(ForceEqual));

The unit:

    unit uChemParserCommonForce;

    interface

    uses
      uMathVector3D,
      Contnrs;

    type

      TForce = class;
      TForceList = class;

      TForce = class
      private
        FElementZ: Integer;
        FValue: TVector3D;
      public
        property ElementZ: Integer read FElementZ;
        property Value: TVector3D read FValue;
        constructor Create(aElementZ: Integer; aX, aY, aZ: Double);
        function ToString(): string; {$IF DEFINED(FPC) OR DEFINED(VER210)} override; {$IFEND}
      end;

      // Mastering Delphi 6 - Chapter 5 -
      TForceList = class(TObjectList)
      protected
        procedure SetObject(Index: Integer; Item: TForce);
        function GetObject(Index: Integer): TForce;
      public
        function Add(Obj: TForce): Integer;
        procedure Insert(Index: Integer; Obj: TForce);
        property Objects[Index: Integer]: TForce read GetObject
          write SetObject; default;
      end;

    function ForceCompare(Item1, Item2: Pointer): Integer;
    function ForceEqual(Item1, Item2: Pointer): Boolean;

    implementation

    uses
      Math, SysUtils;

    function ForceCompare(Item1, Item2: Pointer): Integer;
    begin
      // Ascendent
      //  Result := CompareValue(TForce(Item1).Value.Len, TForce(Item2).Value.Len);
      // Descendent
      Result := CompareValue(TForce(Item2).Value.Len, TForce(Item1).Value.Len);
    end;

    function ForceEqual(Item1, Item2: Pointer): Boolean;
    begin
      Result := TForce(Item1).ElementZ = TForce(Item2).ElementZ;
    end;

    constructor TForce.Create(aElementZ: Integer; aX, aY, aZ: Double);
    begin
      FElementZ := aElementZ;
      FValue := TVector3D.Create(aX, aY, aZ);
    end;

    function TForce.ToString: string;
    begin
      Result := IntToStr(FElementZ) + ' X: ' + FloatToStr(FValue.X) + ' Y: ' +
        FloatToStr(FValue.Y) + ' Z: ' + FloatToStr(FValue.Z);
    end;

    { TForceList }

    function TForceList.Add(Obj: TForce): Integer;
    begin
      Result := inherited Add(Obj);
    end;

    procedure TForceList.SetObject(Index: Integer; Item: TForce);
    begin
      inherited SetItem(Index, Item);
    end;

    function TForceList.GetObject(Index: Integer): TForce;
    begin
      Result := inherited GetItem(Index) as TForce;
    end;

    procedure TForceList.Insert(Index: Integer; Obj: TForce);
    begin
      inherited Insert(Index, Obj);
    end;

    end.
Robb answered 10/3, 2011 at 21:15 Comment(9)
Does IndexOf have more than one meaningful implementation?Chemiluminescence
@David: Thank you for your time! I would think the default IndexOf compares the reference address? I somehow wonders whether IndexOf can use other criteria.Robb
I think is better if you tell us what are you trying to accomplish, this looks like you want two lists with different order, or like you don't know how IndexOf works.Spitfire
No other implementation makes senseChemiluminescence
@jachguate: Thank you for your time! I want to sort an instance of TForceList by the value field of its TForce objects. However, when searching for the TForce object with Element Z, I want to simply use Element Z as the criteria.Robb
@David: Do you mean it is not possible to call IndexOf based on custom criteria? (I mean, Sort can use custom criteria; Furthermore, the generic list seems to offer this possibility?)Robb
@Xichen For the criteria for what? it looks like you may want to maintain two separate lists, ordered with different criteria, and use one or another when appropriate to your needs. Other approach is to re-order the list. Anyway, IndexOf is a linear search and always compares references. If you have an ordered list, you may want to perform a BinarySearch, what is more performant on your case depends on the particular use of those objects.Spitfire
@Xichen take a look at the source code of IndexOf method, where there's no criteria involved at all, and take a look at the source code of Sort method, you'll understand why your requirement does not make sense.Spitfire
@Xichen it is trivial to write your own search for TObjectList, which can use whatever criteria you want. But changing IndexOf would not be prudent. It has a very well-defined job and changing the meaning, if that were indeed possible, would create confusion.Chemiluminescence
J
2

The non-generic TObjectList uses TList.IndexOf, which simply iterates through the internal array and compares pointers.

Likewise, the generic TObjectList<T> uses TList<T>.IndexOf, which uses an IComparer. TList<T>.Sort uses TArray.Sort<T> passing in whatever IComparer was assigned at the list's creation.

The comparer is private and only assigned in the list constructor so I don't see an easy way to override this behavior.

Update

TList<T> provides and overloaded Sort that accepts a comparer as an argument, without modifying the private comparer. So you can sort using one comparer and indexof can use a different one.

Jarred answered 10/3, 2011 at 21:37 Comment(0)
S
2

The entire idea behind the Sort method is it really sorts the list... in other words, after calling the sort method, the physical order of the elements on the list is changed to meet the sort criteria.

The IndexOf method, as you can see in your own Delphi RTL code, is just a linear search by reference returning the physical index of the first matching element.

The index returned can be used to retrieve the object on the list, like this:

SomeIndex := AList.IndexOf(SomeObject);
//more code...
//you can re-use the reference...
//and maybe more...
SomeObject := AList[SomeIndex];

You'll see why, the IndexOf method shall not return a index based on a different criteria than the physical order of the list... and it happens if you call Sort first, the physical order is reflecting the passed sort criteria.

That said, it looks like you may want to

  • maintain two different lists, sorted with different criteria and use one or another when appropriate.
  • re-sort the list based on the applicable criteria for the operation your application is processing at a given time.

What is more performant depends on how your application use those objects, the amount of data it is processing and even the memory available to your process at runtime.

Spitfire answered 10/3, 2011 at 21:57 Comment(1)
Thank you very much for your time and kind efforts!Robb
F
1

Not with the standard TObjectList. It (actually the base TList) is written to support a custom sort function using CustomSort, but there's no such provision for a custom IndexOf. Of course, you could write your own implementation of something that works that way.

Forging answered 10/3, 2011 at 21:25 Comment(1)
Thank you very much for your comments! I guess then I have to use a TStringList aside by the TForceList, and use TStringList.IndexOfName.Robb

© 2022 - 2024 — McMap. All rights reserved.