Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is MATLAB sensitive to order of fields in a struct array assignment?

First I specify A as a struct, and two other structs: B having the same order of elements, and C having a different order of elements.

A.x = 11;
A.y = 11;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A = B;      %// Works fine
A = C;      %// Works fine

Assigning A to B and to C works, which is the behavior I expect from structs - order of elements should not matter.

Now I specify A as a struct array instead of a struct and try to assign one of its elements to B and C respectively:

clear;

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   %// Note that I am specifying
C.x = 32;   %// y first and x second

A(1) = B;   %// Works fine
A(1) = C;   %// Error!

Suddenly MATLAB is complaining with the error:

subscripted assignment between dissimilar structures

Does anyone have any idea why this happens and how to fix it in an elegant way?

like image 874
MGA Avatar asked Apr 28 '16 20:04

MGA


2 Answers

This is likely happening because the built-in subsasgn call for the struct probably just compares the output of fieldnames (which is dependent upon field order) for the source and destination structs and does not perform any sorting prior to the comparison (likely because of the performance hit of sorting two cell arrays for every assignment). If there is a difference (as in the case you've shown) then an error is thrown and the assignment is aborted.

The easiest way to get around this is to use orderfields on the source struct and specify that you want the ordering to match the destination struct's using the second input argument.

A = struct('x', 11, 'y', 12);
B = struct('y', 21, 'x', 22);

%// Ensure that the fields of B are in the same order as they are in A
A(1) = orderfields(B, A);

In my personal opinion, I think that subsasgn should do this itself for struct inputs because the operation is relatively fast (as there is no sorting and the underlying data of the struct isn't copied) but allows for more flexible struct assignment.

If, on the other hand you aren't doing direct assignment, but simply want to append two structs, the order of the fields does not matter and the order of the fields is inherited from the first struct that is encountered.

%// Uses the ordering of the fields in A
C = cat(1, A, B);

%// Uses the ordering of the fields in B
D = cat(1, B, A);

Update

I just noticed that you showed in your original post that the following worked because the order didn't matter.

A = B;

This works because this assignment is not dependent upon the datatype of A. In this scenario, MATLAB removes the data that A was referencing before the assignment and then re-assigns A it to the point to B. We could even make A a cell array and perform the above assignment with no issues.

A = cell(2);
B = struct('y', 21, 'x', 22);

%// No errors here!
A = B; 

This assignment does not make a call to subsasgn (which deals only with subscript assignment) and therefore would not be expected to exhibit the issue you encountered.

like image 171
Suever Avatar answered Nov 12 '22 19:11

Suever


One way that I have solved this in the past is to create a "null" version of the structure, similar to creating a constructor for an object.

%% define null struct
null_struct.x = 0;
null_struct.y = 0;

%% Now, initialize all structs with it
A=null_struct;
B=null_struct;
C=null_struct;

%% You can even initialize large arrays
Z(1:1000, 1:1000) = null_struct;

Then, you can populate the structures in any order you like. You can even pass the "empty" structures into a function, and allow the function to populate the values, and the function doesn't have to be careful about the order the values are assigned.

A(1).x = 11;
A(1).y = 12;

B.x = 21;
B.y = 22;

C.y = 31;   % Note that I'm specifying
C.x = 32;   % y first and x second

A(1) = B;   % Works fine
A(1) = C;   % Also works fine!

Initializing your data structures is very good programming practice, and for large struct arrays, it actually saves a lot of time to do the initialization up front. Even if you have to initialize more elements than needed, and truncate the array at the end, it is generally worth it.

EDIT: Explanation for your error: The reason that MATLAB throws an error in your original example is that internally (in the C-code backend), MATLAB stores the field names in an ordered array of character strings, and maps the names to corresponding field indices. When you perform an assignment like

A = C;

MATLAB first checks that the two lists of fieldnames match, which requires that the lists be identical, including in the same order. If they are, then it maps the field values in order from the rhs to the lhs.

like image 23
gariepy Avatar answered Nov 12 '22 17:11

gariepy