delphifive
delphifive

Reputation: 123

Delphi how to search index of type record array

Is it any better way for finding the Index of a Typed array ?
is it any way to store each record in a Tlist or something and then find the index.

for example I like to have index=FindIndexof(12345) and the function to return the Index of game array.
At the moment I am using the following code but I think is wrong because I am storing the event_id in two locations in memory.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TMyEvent = record
    Event_id: longint;
    Myarray: array[0..2] of Integer;
    MyString: string;
  end;

const max_events=100;

var
  Form1: TForm1;
  MyEvents:Array[0..max_events] of TMyEvent;
  MyListIndex:  TStringlist;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var x:Integer;
begin
  randomize;
  MyListIndex:=TStringlist.create;
  for x:=0 to max_events do
  begin
    with myEvents[x] do
    begin
      Event_id:=Random(10000)+1;
      Myarray[0]:=1;
      Myarray[1]:=2;
      MyListIndex.add('^'+formatfloat('0',Event_id)+'^');
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var MyIndexId:Longint;
begin
  MyIndexId:=MyListIndex.indexof('^12345^');
  // and after I can process
  // myEvents[MyIndexId].Event_id
end;

end.

Upvotes: 0

Views: 2655

Answers (2)

User007
User007

Reputation: 187

If you use TList<T> instead of TArray<T> then your live becomes easier. If you pass an IComparer<T> to your TList<T> constructor then the Sort and IndexOf methods can use it to sort/find items. I show the example for anonymous and class method IComparer<TMyRec> usage. You can comment out the one you not prefer.

intrface

type
  TMyRec = packed record
    a, b : integer;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    function compareRecs( const left_, right_ : TMyRec ) : integer;
  public
    { Public declarations }
  end;

implementation

uses
  Generics.Collections, Generics.Defaults;

function TForm1.compareRecs( const left_, right_ : TMyRec ) : integer;
begin
  result := left_.a - right_.a;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  iC : IComparer<TMyRec>;
  aL : TList<TMyRec>;
  aMR : TMyRec;
  i : integer;
begin
  iC := TComparer<TMyRec>.Construct( function ( const left_, right_ : TMyRec ) : integer begin result := left_.a - right_.a; end );
  //iC := TComparer<TMyRec>.Construct( compareRecs );
  aL := TList<TMyRec>.create( iC );
  try
    for i := 1 to 5 do
    begin
      aMR.a := 6-i;
      aMR.b := i;
      aL.Add( aMR );
    end;
    // The order of the items is in reverse order (by TMyRec.a)
    aL.Sort;
    // The order of the items is in the right order (by TMyRec.a)
    aMR.a := 3;
    i := aL.indexOf( aMR );
    // i = 2
  finally
    aL.Free;
  end;
end;

When you call indexOf method by a record, the result index just depends on the record values used in the comparer function. In this case TMyRec.a. To use generic lists (TList<T>) you have to use the Generics.Collections unit. If you want to use custom ordering, use the IComparer<T> implementations. For this you have to use the `Generics.Defaults' unit.

Upvotes: 0

Johan
Johan

Reputation: 76753

It depends.

If you do these lookups often you'll want to sort the array first and then do a binary search. If you insert items all the time and don't do lookups very often then you'll just want to leave things as is.

Here's how to sort your items, first put your records in a array.

//Pull in TArray.Sort and TArray.BinarySearch
uses System.Generics.Collections;


Events: TArray<TMyEvent>;
....
SetLength(Events, MyEventCount); 
for i:= 0 to MyEventCount-1 do begin
  //Read in events
end;

Now sort them using

procedure SortEvents(var Events: TArray<TMyEvent>);
begin
  TArray.Sort<TMyEvent>(Events, TDelegatedComparer<TMyEvent>.Construct(
    function(const Left, Right: TMyEvent): Integer
    begin
      if Left.EventId > Right.EventId then Exit(1);
      if Left.EventId < Right.EventId then Exit(-1);
      Result:= 0;  //or raise an error if duplicates are not allowed.
    end
  ));
end;

See: TArray.Sort<T>

If you want to search do:

function EventByIndex(const Events: TArray<TMyEvent>; EventId: longint; out Index: integer): TMyEvent;
var
  Dummy: TMyEvent;
  Found: boolean;
begin
  Dummy.EventId:= EventId;
  Found:= TArray.BinarySearch(Events,  Dummy, Index, 
    TDelegatedComparer<TMyEvent>.Construct(
    function(const Left, Right: TMyEvent): Integer
    begin
      if Left.EventId > Right.EventId then Exit(1);
      if Left.EventId < Right.EventId then Exit(-1);
      Result:= 0;  //or raise an error if duplicates are not allowed.
    end
  ));
  if Found then Result:= Events[Index]
  else Index:= -1;
end; 

See: TArray.BinarySearch<T>

Note that you can only do a BinarySearch if the array is in sorted order.
If EventID is not unique this function will only return a single result, but of course the items with the same EventId will be right next to the one returned so you should be able to work from there.

If you just want to do a lineair search do:

function EventIndexOf(const Events: TArray<MyEvent>; EventId: longint): integer;
var
  i: integer;
begin
  for i:= 0 to High(Events) do if Events[i].EventId = EventId then Exit(i);
end;

Remarks
Obviously there is no need to store duplicate data. Store numbers in an Int (or Int64 if they're huge), store text in a string.
Please do not abuse a TStringList to store record data. A TList<TSomeRecord> or TArray<TSomeRecord> is much better suited to that purpose.

Global variables are bad, try to never write code like this:

unit X;
interface
...
var
  Form1: TForm1;
  MyEvents:Array[0..max_events] of TMyEvent;
  MyListIndex:  TStringlist;
implementation ....

Put our own vars in the private section of TForm1 (or whatever class suits your purpose) instead.

Upvotes: 2

Related Questions