23

The straight-forward way to append one element to a dynamic array is as follows:

SetLength(SomeDynamicArray, Length(SomeDynamicArray) + 1);
SomeDynamicArray[High(SomeDynamicArray)] := NewElement;

Disregarding performance issues due to memory fragmentation, is there a way to do this in one line?

4
  • I would like something SomeDynamicArray.Add(NewElement), maybe add a QC?
    – André
    Commented Apr 22, 2011 at 13:56
  • There are no performance implications of using dynamic arrays, if you use them properly. Quite on the contrary, they have essentially no overhead at all! But if the above patterns is "very frequent" in your code, I would personally be very sceptical about your code. Commented Apr 22, 2011 at 14:05
  • Please be less aggressive we are trying to help. I already answered the question you ask. There is no such function. Commented Apr 22, 2011 at 14:19
  • 19
    It's very hard to work out what level of knowledge people have. Please tolerate us getting it wrong on occasion and politely put us right. Commented Apr 22, 2011 at 14:27

8 Answers 8

30

Starting with Delphi XE7 you can do:

SomeDynamicArray := SomeDynamicArray + [NewElement];

ref: Marco Tech Blog, September 18, 2014 : Dynamic Arrays in Delphi XE7

3
  • 2
    Does this method reallocate memory? Commented Jan 11, 2021 at 9:48
  • 2
    I have a dream, that one day, Delphi would have comprehensive documentation, so no man would have to hunt these essential tidbits online.
    – Doege
    Commented Mar 14, 2023 at 9:36
  • 2
    @Doege For a language reference the Object Pascal Handbook by Marco Cantu is currently available for free as a download from Embarcadero and is pretty solid.
    – Brian
    Commented Mar 14, 2023 at 12:15
26

Here's a hack with generics which only works with TArray<T>:

type
  TAppender<T> = class
    class procedure Append(var Arr: TArray<T>; Value: T);
  end;

class procedure TAppender<T>.Append;
begin
  SetLength(Arr, Length(Arr)+1);
  Arr[High(Arr)] := Value;
end;

Usage:

var
  TestArray: TArray<Integer>;

begin
  TAppender<Integer>.Append(TestArray, 5);
end.
2
  • did they fix the bug that meant high(arr) would not compile for TArray? Commented Apr 22, 2011 at 14:57
  • Not sure about that bug (never used TArray before this), but this code compiles fine on Delphi XE. Commented Apr 22, 2011 at 15:17
17

Every time you call SetLength the memory gets reallocated. Maybe the entire array needs to be copied to a different location. And you who just wanted to add a single element to the array!

Basically: never do this. There are two ways out of it. The simplest case is if you beforehand know the maximum size of the array:

procedure Example1;
var
  data: array of string;
  ActualLength: integer;

  procedure AddElement(const Str: string);
  begin
    data[ActualLength] := Str;
    inc(ActualLength);
  end;

begin

  ActualLength := 0;
  SetLength(data, KNOWN_UPPER_BOUND);

  for ...
    while ...
      repeat ...
        AddElement(SomeString);

  SetLength(data, ActualLength);

end;

Here is a practial example of this approach.

If you don't know any upper bound a priori, then allocate in large chunks:

procedure Example2;
const
  ALLOC_BY = 1024;
var
  data: array of string;
  ActualLength: integer;

  procedure AddElement(const Str: string);
  begin
    if ActualLength = length(data) then
      SetLength(data, length(data) + ALLOC_BY);

    data[ActualLength] := Str;
    inc(ActualLength);
  end;

begin

  ActualLength := 0;
  SetLength(data, ALLOC_BY);

  for ...
    while ...
      repeat ...
        AddElement(SomeString);

  SetLength(data, ActualLength);

end;

This second approach is implemented in the run-time library's TList<T>, TObjectList<T>, TStringList etc. Hence, when you use these classes, it is perfectly fine to append the list one item at a time.

5
  • +1 for providing better "growing" without needing generics (and thus D2009+) Commented Apr 22, 2011 at 14:10
  • 1
    @an (or the) anonymous downvoter: And how is this not useful?! @Andreas: Thanks!
    – Andriy M
    Commented Apr 22, 2011 at 14:58
  • 3
    I found this post not useful (and downvoted it) because: 1) it does not answer the stated question 2) it tells me something I already knew 3) the suggested solution goes in the direction diametrically opposite of what I need (code simplicity and terseness) 4) it commands the reader to always [not] do something, which does not apply for all cases (such as mine). Commented Apr 22, 2011 at 15:38
  • 1
    As the person who asked the question you may indeed have more reasons to downvote an otherwise valid answer. I may not agree with you on some of your points but basically I admit you are right. In any case, that's really nice of you to state your reasons behind the downvoting.
    – Andriy M
    Commented Apr 22, 2011 at 16:23
  • 2
    In my own experience I put ALLOC_BY as 125% of actual Length. It is similar to TList grow steps.
    – ALZ
    Commented Jan 24, 2014 at 22:55
7

That's an anti-pattern that results in memory fragmentation. Instead use Generics.Collections.TList<T> and call the Add method to add new items.

There is no one liner to extend an array and add an item. You could create your own dynamic array wrapper using generics to do this should you so desire. Essentially that's what Generics.Collections.TList<T> is.

1
  • 1
    +1 Yes, the grow function of (D2009) TList class is much better than growing by a single item. However, depending on how large the list is going to get, you may want to consider creating a descendant and overriding Grow to suit specific scenario's. As it stands, TList will never grow by more than 25% once the capacity reaches over 64 items. We have found doubling the list every time the capacity is reached to be more performant and "non-fragmentation friendly". Commented Apr 22, 2011 at 14:09
5

There is handy Insert procedure, that you can use like this:

Insert([NewElement], SomeDynamicArray, High(SomeDynamicArray));
4
MyList.Add(myobject);

IMO Dynamic arrays should only be used when at compile time you don't know the exact size of the array but at run time you will know.

If you need to continually manipulate your array size, you shouldn't be using an array but a TList or one of its descendants, as others have mentioned: TObjectList, TInterfaceList, TStringList.Objects[] can all be used (and abused) and there are some new ones as well, and TList for primitive types. TList used to be something of a pain before generics were introduced into Delphi - you had to work with pointers - but with generics: TList <T> it's very easy.

Also, use the capacity property of any list you create - it will pre-allocate the given amount of memory so your code doesn't cause lots of memory to get juggled around every time you perform an operation on your list. (If you go beyond the capacity you allocated, memory manager will give you more memory at runtime- you wan't fail - see Delphi Help)

3

If you have Delphi 2009 or later, and you really want to shorten the above piece of code, you could try something like

type
  DataArray<T> = record
    Data: array of T;
    procedure Append(const Value: T);
    function Count: integer;
  end;


{ DataArray<T> }

procedure DataArray<T>.Append(const Value: T);
begin
  SetLength(Data, length(Data) + 1);
  Data[high(Data)] := Value;
end;

function DataArray<T>.Count: integer;
begin
  result := length(Data);
end;

Then you can do

procedure TForm1.FormCreate(Sender: TObject);
var
  data: DataArray<string>;
begin
  data.Append('Alpha');
  data.Append('Beta');
  Caption := IntToStr(data.Count) + ': ' data.Data[0] + ' & ' + data.Data[1];
end;
3
  • Would it be possible to instead declare a templated function that operates on the dynamic array type? (So you're not forced to use a custom type just for this.) Commented Apr 22, 2011 at 14:26
  • @CyberShadow: I can't think of any way of doing that, I'm, afraid. Commented Apr 22, 2011 at 14:28
  • It seems possible with a class function (see my answer below). Commented Apr 22, 2011 at 14:43
2

You can simply use this code to append an element to end of any dynamic array. fast and short.

...
var
  myArray: array of Integer;
  element: Integer;
...
Insert(element, myArray, MaxInt);
...
3
  • 3
    Maybe you didn't notice this at the time you posted your answer but using Insert has already been suggested by Torbins. There's absolutely no need to post an answer offering the same solution, unless you'd like to offer a valuable insight, perhaps, but even then you could just suggest an edit to the existing answer if your insight doesn't really change it fundamentally (which yours doesn't, as it happens), cheers.
    – Andriy M
    Commented Jul 14, 2018 at 8:19
  • @AndriyM Yup! but I just want to show a shortest and cleanest (and a few fastest in long loops) code. High() function may cause a little delay in long loops. AND that code will always push first element of array to end of array each time. and needs one or two more lines to correct this. but this code doesn't.
    – Farshad H.
    Commented Jul 15, 2018 at 9:08
  • Have you considered updating your answer with those details? I'm not sure if what you are saying is true but if those considerations were the reason for your posting almost – but not quite – an identical answer to someone else's, then IMHO they should be included in your post.
    – Andriy M
    Commented Jul 15, 2018 at 14:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.