SuperCollider Help Docs Example on Arrays

Hi. While working through a tutorial on arrays and mentioned Lists at the end. I want to understand more about how an example piece of code worked. I tried breaking it down to simpler parts and experimenting with it. However, I just could not get things to work consistently.

Here is the example code and the weblink to the Help topic:
https://doc.sccode.org/Classes/List.html
The code:

// Create a new empty collections with size 3:
x = Array.new(3); 
y = Array.new(3); 
z = List.new(3);

// Try to add 7 items to the array and list.
// If the array exceeds maxSize, it will only
// grow to an internally specified max size.
( 
7.do { |i|
    x.add(i);
    y = y.add(i); // reassign array y on each add
    z.add(i);
};
)

// Attempt to access the 5th item:
x.at(4); // -> nil (out of bounds)
y.at(4); // -> 4
z.at(4); // -> 4

// x grew only to size 4
x.postln; // -> [ 0, 1, 2, 3 ]
y.postln; // -> [ 0, 1, 2, 3, 4, 5, 6 ]
z.postln; // -> List[ 0, 1, 2, 3, 4, 5, 6 ]

Looking at the top where it provides:
x = Array.new(3);
I looked up what 3 meant in that context and that is the maxSize for the array. It says that is supposed to be the maximum size of the array. I can see where it expands the array using that code up to 4 items. So maybe the number 3 is the index number? However, if I reduce the number to 2, I still get the array created to be [0, 1, 2, 3]. In addition, I can use x.add(some #); and it will keep adding numbers to the array.
I just do not understand why changing the maxSize number does not have the effect I think it should.

The behavior you’re reporting is known and (tbh) it’s expected.

maxSize is, for Array, indeed the maximum size of that array. (Its current size may be smaller.) So what happens if you .add an item to an array that is full (where its active size == maxSize)? This particular array can’t grow. So instead, it returns a new array whose maxSize is twice the old array’s maxSize.

When you do y = y.add(...), then y’s value changes to point to the new, larger array. But x.add by itself doesn’t update the variable. It does the .add and creates the expanded array, but the new array is not assigned to a variable, so it’s unreachable and will get garbage-collected. x remains the old array, so it looks like nothing happened.

^^ see the note near the top here: Array | SuperCollider 3.14.1 Help

maxSize only controls the array’s initial memory allocation. It doesn’t constrain the behavior of .add, .insert etc methods. .add cannot expand an array instance, but it is free to return a larger array (different instance).

The other detail (which I’m not sure is documented) is that IIRC arrays allocate enough space for the next power of 2 greater than or equal to the given size. If you ask for 3, it will allocate 4 spaces. Also I think 4 is the minimum, since it’s wasteful to allocate 2 spaces and then very quickly have to allocate 4 and de-allocate the 2.

hjh

Thanks for that information. Your paragraph at the actually explains a lot of the behaviour I was seeing as I was trying different things, even if I still do not understand it all. I was trying different maxSize numbers, sometimes increasing the number by 1 each try and the results were not making sense.

I was getting frustrated with what seemed should have been very simple. If I had worked at it a little longer and tried increasingly larger numbers, I might have been able to pick out the pattern. I am an amateur programmer with not a whole lot of experience.

It is good to understand what is going to here… but practically, it is almost always easier to create an empty Array and just always reassign it every time you add to it. Although it looks like you want to create an array with a starting size because that is the constructor method, you almost never actually care, or even know its final size when you create it. Don’t worry about the performance of doing this, it is trivial.

a = [];
10.do { |i| a = a.add(i) }

… or just always use List

a = List();
10.do { |i| a.add(i) }

In general, yes – I also note that you can flip this formulation on its head and it’s still true: “There’s no harm in allocating some modest number of extra slots that you don’t end up using. Don’t worry about the memory impact of doing this, it is trivial” (unless you’re creating hundreds of thousands of arrays with a large maxSize, but that’s not a common case).

In some places in my code, I do an Array(100) (I guess that really should be 128) and the subsequent loop might add 6, or 50, items.

But I’m not consistent about that. Modern systems have enough CPU cycles and memory to burn that it’s fine either way.

hjh