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

狂风中的少年 提交于 2019-12-11 08:24:30

问题


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.

回答1:


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.




回答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.




回答3:


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.



来源:https://stackoverflow.com/questions/5266008/can-i-pass-in-one-function-for-tobjectlist-indexof-and-another-function-for-tob

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!