views:

104

answers:

3

Hello,

In my application, I've created the TList type list, intended to store Integers or Doubles:

TKList<T> = class
  private
    FItems: TList<T>;
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; const Value: T);
    function GetMaxValue(): T;
    function GetMinValue(): T;
  public
    constructor Create; overload;
    constructor Create(const AKList: TKList<T>); overload;
    destructor Destroy; override;
    procedure Assign(const AKList: TKList<T>);
    function Add(const AValue: T): Integer;
    procedure Clear;
    function Count: Integer;
    procedure Invert;
    function ToString: string; override;
    function Info: string;
    property Values[Index: Integer]: T read GetItem write SetItem; default;
  end;

How can I implement Invert() procedure to invert values in generic List?

Thanks in advance.

+5  A: 

Assuming you mean to Reverse the array as in you have values 1, 3, 5 after calling this function you want to have 5, 3, 1

Then, you could implement the procedure like this.

procedure TKList<T>.Invert;
var
  I: Integer;
begin
  for I := 0 to (Count - 1) div 2 do
    FItems.Exchange(I, Count - I - 1);
end;

Altho I would suggest Reverse as it's name, since Invert is kind of confusing.

Aldo
Invert means to change the sign of each value. So 1, -3, 5 converts to -1, 3 and -5.
Keeper
+1  A: 

There's no way to specify constraints on generics such that you can require the types to be numbers, so there's no way you can use numeric operators on the values in your list. Craig Stuntz wrote a series of posts describing how to build a generic statistical library, and he came up against the same problem. He solved it by providing additional arguments to his functions so that the caller could provide implementations for the type-specific numeric operations — the template method pattern. Here's how he declared the Average operation:

type
  TBinaryOp<T> = reference to function(ALeft, ARight: T): T

  TStatistics<T> = class
    public
      class function Average(const AData: TEnumerable<T>;
                             AAdder, ADivider: TBinaryOp<T>;
                             AMapper: TFunc<integer, T>): T; overload;

Callers of that function need to provide their own code for adding, dividing, and "mapping" the generic type. (Mapping is covered in a later post and isn't important here.) You could write your Invert function like this:

type
  TUnaryOp<T> = reference to function(Arg: T): T;

  TKList<T> = class
    procedure Invert(ANegater: TUnaryOp<T>);

procedure TKList<T>.Invert;
var
  i: Integer;
begin
  for i := 0 to Pred(Count) do
    Values[i] := ANegater(Values[i]);
end;

To make it more convenient to call the methods without having to provide the extra arguments all the time, Stuntz showed how to declare a type-specific descendant that provides the right arguments. You could do it like this:

type
  TIntKList = class(TKList<Integer>)
  private
    class function Negate(Arg: Integer): Integer;
  public
    procedure Invert;
  end;

procedure TIntKList.Invert;
begin
  inherited Invert(Negate);
end;

You can provide type-specific descendants for the common numeric types, and if consumers of your class need to use other number-like types, they can provide their own implementations for the basic numeric operations without having to re-implement your entire list class.

Rob Kennedy
+1  A: 

Thanks Rob, I got it.

What advantages/disadvantages has the following approach:

procedure TKList<T>.Invert;
var
  i: Integer;
  Val: TValue;
begin

  if TTypeInfo(TypeInfo(T)^).Kind = tkInteger then
  begin
    for i := 0 to FItems.Count - 1 do
    begin
      Val := TValue.From<T>(FItems[i]);
      TValue.From<Integer>(-Val.AsInteger).AsType<T>;
    end;  
  end
  else if TTypeInfo(TypeInfo(T)^).Kind = tkFloat then
  begin
    for i := 0 to FItems.Count - 1 do
    begin
      Val := TValue.From<T>(FItems[i]);
      FItems[i] := TValue.From<Double>(-Val.AsExtended).AsType<T>;
    end;
  end;

end;
Keeper
If I create some other number-like type, I can't store it in your list that supposedly handles numbers. Also, you should decide whether you're using Extended or Double. And if the "generic" class can only handle two types, I wonder whether it's worth being generic at all.
Rob Kennedy