3

Growing an array, e.g. through x = [x, a] in a loop, is usually frowned upon in Matlab programming, because it leads to one or more resize operations, and therefore preallocation is usually the better option. However, in some contexts, for example as a reduction assignment in a parfor block, it can have advantages. For that purpose, the array has to be created as zero-size. For numerical arrays, the expression for that is [], for cell arrays {}.

For struct arrays, it is not so clear cut. For example initialization as struct() does not create a zero-size struct array, but a 1x1 struct array with no fields. This leads to the error

Error using horzcat
Number of fields in structure arrays being concatenated do not match.
Concatenation of structure arrays requires that these arrays have the same set of fields.

while growing, because the field structure of appended structs is incompatible with the "no fields" struct.

How to initialize a zero-size struct array for growing?

A. Donda
  • 8,381
  • 2
  • 20
  • 49
  • This is an issue I ran into many times, and now I finally figured it out, so I thought I'd share the solution. – A. Donda Aug 26 '18 at 03:11
  • 1
    You can use `s = struct('field1', {}, 'field2', {})` to create a 0×0 struct array with the specified fields – Luis Mendo Aug 26 '18 at 04:14
  • Also, `x=x[x,a]` is highly inefficient. Use `x(end+1)=a`. https://stackoverflow.com/a/48990695/7328782 – Cris Luengo Aug 26 '18 at 04:22
  • @LuisMendo, interesting, I didn't know that. However, in my application the fields are defined by a function returning the struct, and I'd rather not put the information on what fields there are into another place, too. The function is subject to change, the loop aggregating results shouldn't be. – A. Donda Aug 26 '18 at 04:24
  • @CrisLuengo, I mentioned the inefficiency, and I also explained why it is sometimes the lesser evil: being able to use reduction assignments. Assigning to `end + 1` isn't possible in parfor loops. Still interesting to know that it is more efficient than concatenation. – A. Donda Aug 26 '18 at 04:26
  • 1
    @A.Donda In that case, `struct([])` creates a 0×0 struct array with no fields – Luis Mendo Aug 26 '18 at 04:33
  • 1
    @LuisMendo, thanks! I incorporated your comments into the answer. – A. Donda Aug 26 '18 at 12:37

2 Answers2

2

It turns out the answer is to initialize to a zero-size numeric array, x = []. Even though it is not a struct array, appending a struct a via x = [x, a] generates a 1x1 struct array, to which further structs with the same fields can be appended.

This unintuitive behavior seems to be a general quirk of the Matlab language, in that [] is of "flexible type". It is initially a zero-size array of doubles, but appending singles makes it a single array, and appending cell arrays makes it a cell array.

As @LuisMendo pointed out in the comments, struct([]) directly gives a zero-size struct array with no fields. This syntax may be preferred because it is less ambiguous. Moreover, s = struct('field1', {}, 'field2', {}) may be used to create a zero-size struct array with defined fields.


I just found that @CrisLuengo's proposal in the comments, appending by assignment to end + 1 instead of concatenation, does not work in the context of the question, "initialize for growing". Assignment cannot change the "type" of the struct, which is defined by its fields. Changing from no fields to the fields of whatever struct is to be appended leads to the error "Subscripted assignment between dissimilar structures." (but see his comment and my answer for clarification)

A. Donda
  • 8,381
  • 2
  • 20
  • 49
  • “but appending singles makes it a single array, and appending cell arrays makes it a cell array”. But you are not appending. You are concatenating with. You do `[[],a]`, which is valid for any of the built-in types `a`. Appending is `x(end+1)=a`. That syntax extends the array and writes `a` into it. Your syntax creates a new array, and then assigns it to `x`. What type `x` was before is not relevant in that assignment. – Cris Luengo Aug 26 '18 at 13:24
  • I use "append" here to mean "replace the original value of x with a value that contains the original value and additionally a new value". It is implemented by concatenation, while it could be implemented by assignment to end+1, but that doesn't make it any less of appending – I refer to the result of the operation. – A. Donda Aug 26 '18 at 13:42
  • You refer to what's happening behind the scenes, OK. But that's not that clearcut either. If the array needs to be resized in order to assign to end+1, it also creates a new array. And what happens behind the scenes in a reduction assignment in a parfor loop is even more complex – there's certainly no concatenation, because the x doesn't even exist on the worker where a is being produced. – A. Donda Aug 26 '18 at 13:45
  • I refer to what you actually do. It looks like you append, but the actual code executed is: concatenate `x` and `a`, assign to `x`. The concatenate part works because `x` is an empty array, it’s type doesn’t matter much. I’m not saying your answer is wrong, I’m trying to explain why this works. – Cris Luengo Aug 26 '18 at 13:52
  • `x(end+1)=a` doesn’t create a new array, it extends it. It might need to reallocate the array to extend it, but it cannot change the type of `x`. The concatenation can change the type. – Cris Luengo Aug 26 '18 at 13:54
  • 1
    And I understand your explanation, but I think incorporating it would need much more convoluted wording, without adding much to its practical usefulness. I suggest I leave my answer as it is, but your comments serve as background info? – A. Donda Aug 26 '18 at 13:57
  • Regarding the comment you just added to your answer: you can append to a struct. I've written an answer to demonstrate how to do it correctly. – Cris Luengo Feb 20 '19 at 21:02
  • OK, you're right, and I upvoted your answer. – Something that wasn't in the question is that I was also looking for an elegant solution. I don't want to be concerned with the field structure of the struct; I simply get structs one-by-one (of the same structure) and want to put them together. In my context, the number of elements isn't that large, so efficiency isn't that relevant either. – A. Donda Feb 21 '19 at 19:32
  • 1
    In that case you can also initialize as `x=[]`, then do `if isempty(x), x = a; else x(end+1) = a; end`. But yes, if you don't grow too many elements, the difference will not be important. This becomes relevant with larger arrays (not depending on the number of fields, but the amount elements in the array). – Cris Luengo Feb 21 '19 at 21:56
2

Growing an array by concatenation

x = [x, 0];

is very slow, see here and here. Instead, one should (if pre-allocation is not possible for whatever reason), grow an array by appending a new element like so:

x(end+1) = 0;

The reason is that [x,0] creates a new array, copying the old data into it, at every single loop iteration, whereas the other form extends the array, needing reallocation only occasionally (it doubles the underlying memory size when the array becomes too small).

To initialize an empty struct array (as suggested by @LuisMendo in a comment) one can do this:

s = struct('field1', {}, 'field2', {});

To append to it, do:

s(end+1) = struct('field1', 1, 'field2', 'x');

Alternatively, one can append by

s(end+1).field1 = 5;
s(end).field2 = 'y';

Note that in this case, end+1 only happens when first appending a new element to the array, subsequent fields are written to the last array element.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120