8

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?

David Heffernan
  • 572,264
  • 40
  • 974
  • 1,389
Roddy
  • 63,052
  • 38
  • 156
  • 264
  • why not use `TList` instead ? – Arioch 'The Jan 13 '14 at 11:03
  • I can't remember ever having to resize a stringlist to hold exactly N elements so I wonder about your use cases? Perhaps other datastructures are a better match for what you need. – Lieven Keersmaekers Jan 13 '14 at 11:28
  • Why do you need to resize the list? This is an important question because it has a significant impact on the best answer. E.g. Resizing because other code expects exactly N elements is quite different from resizing to avoid redundant memory overhead for extremely large lists when you know exactly how many strings you want to hold. – Disillusioned Jan 13 '14 at 11:46
  • @CraigYoung Resizing Because I have a visual component (eg TValueListEditor) that must display X items. – Roddy Jan 13 '14 at 11:50

4 Answers4

7

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.

Disillusioned
  • 13,820
  • 3
  • 39
  • 75
jpfollenius
  • 15,826
  • 9
  • 83
  • 148
3

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);
David Heffernan
  • 572,264
  • 40
  • 974
  • 1,389
3

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.

Community
  • 1
  • 1
Disillusioned
  • 13,820
  • 3
  • 39
  • 75
  • 1
    I'd say the 'unfortunate drawback' is that `Count` isn't writeable. While they're related, Capacity represents a totally different concept to Count. – Roddy Jan 13 '14 at 13:55
  • @Roddy True. After all the closely related `TList` allowed `Count` to be writeable. There really isn't any reason to make one writeable and not the other. – Disillusioned Jan 13 '14 at 15:14
  • @DavidHeffernan You're wrong about that. (Perhaps you're simply misunderstanding what I'm referring to.) The only thing `Grow` does is efficiently scale the memory allocation of the internal list as the number of strings increases. However, it does nothing to clear the newly allocated memory, which is why empty strings need to be added in a loop. Each new string also assigns an associated object reference (to **nil**). This is highly inefficient in comparison to `FillChar` over the newly allocated memory. – Disillusioned Jan 13 '14 at 20:40
  • `Grow` is implemented with a call to `SetCapacity`. That is implemented with a call to `SetLength`. That in turn initializes the new memory with a single call to `FillChar`. – David Heffernan Jan 20 '14 at 20:31
0
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);