Using Name/Value Pairs : Part One

by Erick Engelke
September 30, 2024

There are many situations where you wish to associate a name/value pair using a TStringList.

eg. list['Erick'] := 'programmer'; list['Joe'] := 'accountant';

TStringList stores this as a list of strings like: list[0] := 'Erick=programmer'; list[1] := 'Joe=accountant';

where the equals sign in the string is the default value of list.NameValueSeparator.

You can replace the NameValueSeparator, which you must do if you want to include equals signs in your Name strings.

So there are some questions I wondered:

  1. How scalable is this for adding or searching name/value pairs. Does it slow down horribly.

  2. Does changing the tstringlist to sorted help, because then it could theoretically use a binary search. How does that affect addition and searching times.

I used a timer function which measures time in milliseconds, and added an arbitrary number of elements (defined as many=5000). More than that number causes page to start to timeout.

My program times:

  • adding one element

  • adding 5000 more

  • finding the 50th value

  • finding the 5001th value

  • replacing the 5000 value (which involves looking up the previous value and replacing it)

Here are the results running on Chrome on a Mac with an M1 processor. All values should be roughly accurate give or take a millisecond (0.001s).

Operation Unsorted (s) Sorted (s)

Adding one:

0.001

0

Adding 5000:

5.935

105.417

Adding one again (at end of list):

0

0.034

Finding 50th value:

0.001

0.002

Finding Many’th value:

0.001

0.003

Replace Many’th value:

0.002

0.053

Adding 5,000 elements took 5.9 seconds unsorted, or 105 seconds when sorted. Adding to a sorted list requires finding and inserting, so it’s more compute intensive.

Clearly sorting doesn’t help and actually slows things down with this implementation. And looking at the source code, it makes sense because the value[] strategy works by scanning from the start of the list to either the end or stopping at the found value linearly.

From a computer science standpoint, this sounds horribly unscalable, and it could be a problem for some situations. But in reality, for lists of a few thousand numbers, it’s not terrible in most cases, taking only 0.001 seconds to find the 5001th value.

One could implement a faster search/replace function, and may need to if doing, say, 5,000 or more elements. But for most typical browser purposes, this is acceptable.

unit arr;

interface

uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebBtns, WebEdits;

const
   Many = 5000;
type


   TForm1 = class(TForm)
      Button1: TButton;
      MultiLineEdit1: TMultiLineEdit;
      procedure Button1Click(Sender: TObject);
   private
      { Private declarations }
      sl : TStringList;
      procedure AddOne;
      procedure AddMany;
      procedure Find50thValue;
      procedure FindManyValue;
      procedure ReplaceManyValue;

      function OutputTimeThis( p : tprocobj ; msg : string ):double;

   public
      { Public declarations }

   end;

  tprocobj = procedure of object;

var
   Form1: TForm1;

implementation



procedure TForm1.AddOne;
begin
   sl.Values[ 'one' ] := 'was one';
end;

procedure TForm1.AddMany;
var
   name : string;
   i : integer;
begin
   for i := 1 to Many do begin
      name := IntToStr( i );
      sl.Values[ name ] := '1 to many...value=' + name;
   end;
end;


procedure TForm1.ReplaceManyValue;
var
  name : string;
begin
   name := IntTOStr( many );
   sl.Values[ name ] := 'replaced value';
end;


procedure TForm1.Find50thValue;
var
  s : string;
begin
  s :=  sl.values[ '50' ];
  MultiLineEdit1.Lines.Add('Find50thValue got ' + s );
end;


procedure TForm1.FindManyValue;
var
  s : string;
begin
  s :=  sl.values[ IntToStr( many ) ];
  MultiLineEdit1.Lines.Add('FindManythValue got ' + s );
end;


procedure Tform1.OutputTimeThis( p : tprocobj ; msg : string );
var
  timer : DateTime;
  seconds : double;
begin
   timer := now;
   p;  // execute the procedure
   seconds := (Integer(now) - Integer(timer)) / 1000;

   if msg <> '' then
      MultiLineEdit1.Lines.Add( msg + ': ' + FloatToStr( seconds ));

end;



procedure TForm1.Button1Click(Sender: TObject);
begin
  sl := TStringList.Create;
  sl.sorted := False;

  MultiLineEdit1.Lines.Add('==========================');
  MultiLineEdit1.Lines.Add('Unsorted');

  OutputTimeThis( AddOne , 'Adding one');
  OutputTimeThis( AddMany , 'Adding ' + IntToStr( many ));
  OutputTimeThis( AddOne , 'Adding one again (at end of list)');
  OutputTimeThis( Find50thValue, 'Finding 50th value');
  OutputTimeThis( FindManyValue, 'Finding Many''th value');
  OutputTimeThis( ReplaceManyValue, 'Replace Many''th value');

  sl.Free;

  sl := TStringList.Create;
  sl.Sorted := True;

  MultiLineEdit1.Lines.Add('==========================');
  MultiLineEdit1.Lines.Add('Sorted');

  OutputTimeThis( AddOne , 'Adding one');
  OutputTimeThis( AddMany , 'Adding ' + IntToStr( many ));
  OutputTimeThis( AddOne , 'Adding one again (at end of list)');
  OutputTimeThis( Find50thValue, 'Finding 50th value');
  OutputTimeThis( FindManyValue, 'Finding Many''th value');
  OutputTimeThis( ReplaceManyValue, 'Replace Many''th value');

end;

end.