Question

I frequently find that I need to 'resize' a a TStringList to hold exactly N elements, either adding additional empty strings to the list, or deleting unneccessary ones.

On a C++ STL container I could use the resize method, but as that doesn't seem to exist, I usually do somethings like this (warning: pseudocode!).

list.beginUpdate;

while list.Count < requiredSize do
begin
   list.add('');
end;

while list.Count > requiredSize do
begin
   list.delete(list.count-1);
end;

list.endUpdate;

Is there a much simpler way of doing this that I've overlooked?

Was it helpful?

Solution

Judging from the implementation of TStringList.Assign, there is no better way to do this. They basically call Clear and add the strings one by one.

You should of course put your code into a utility method:

procedure ResizeStringList(List : TStrings; ANewSize: Integer);
begin
...
end;

Or you could use a class helper to make your method appear to be part of TStringList itself.

OTHER TIPS

The method in your question is the best you can do. You can make it cleaner if you use a class helper. For instance:

type
  TStringsHelper = class helper for TStrings
    procedure SetCount(Value: Integer);
  end;

procedure TStringsHelper.SetCount(Value: Integer);
begin
  BeginUpdate;
  try
    while Count<Value do
      Add('');
    while Count>Value do
      Delete(Count-1);
  finally
    EndUpdate;
  end;
end;

And then you can write:

List.SetCount(requiredSize);

The Capacity property is almost ideal because it will allocate the correct number of entries in the internal array. However, it has the unfortunate drawbacks that:

  • Newly allocated memory is not initialised.
  • The number of elements Strings.Count is not updated.

Since the Delphi component architecture refers to the base type TStrings, you are able to provide your concrete subclass that can support more efficient resizing functionality. E.g. consider the following implementation of TList.SetCount.

procedure TList.SetCount(NewCount: Integer);
var
  I: Integer;
begin
  if (NewCount < 0) or (NewCount > MaxListSize) then
    Error(@SListCountError, NewCount);
  if NewCount > FCapacity then
    SetCapacity(NewCount);
  if NewCount > FCount then
    FillChar(FList^[FCount], (NewCount - FCount) * SizeOf(Pointer), 0)
  else
    for I := FCount - 1 downto NewCount do
      Delete(I);
  FCount := NewCount;
end;

After updating Capacity, if there is newly allocated memory, it is initialised using FillChar. This is much more efficient than adding / deleting items one at a time.

So you could either provide your own independent concrete implementation of a TStrings subclass, or simply make a copy of Delphi's TStringList which includes an appropriate SetCount method.

However that said, I find it unlikely that this section of code will suffer any performance concerns, so your own solution wrapped in appropriate utility methods would suffice. David's answer is also good, though personally I don't consider the "class helper" feature to be that useful. The "old way" of implementing class helpers is much more versatile.

var
    List:  TStringList;

Assert(requiredSize >= 0);
if requiredSize > List.Count then
    List.Capacity := requiredSize
else
    while List.Count > requiredSize do
        List.Delete(List.Count - 1);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top