fffrost
fffrost

Reputation: 1767

combining 2 MATLAB structs

I have two small structures that I would like to combine but I can't find the right solution. Struct A and B have one column in common (same string values), although it is important that the common values are not necessarily in the same order between the two. For the sake of example, I put them in a different order. I would like to achieve what is in struct C below, where the info from both structures are organised together:

A(1).condition = 'con1';
A(1).y = rand(10, 1);
A(2).condition = 'con2';
A(2).y = rand(10, 1);

B(1).condition = 'con2';
B(1).z = normrnd(0,1);
B(2).condition = 'con1';
B(2).z = normrnd(0,1);

% Desired output:
C = A;
C(1).z = B(2).z;
C(2).z = B(1).z

Upvotes: 2

Views: 114

Answers (3)

Cris Luengo
Cris Luengo

Reputation: 60444

You need to:

  1. Find the order in which the elements of one struct array match the other, based on the value of condition.
  2. Copy each field in B(i) to the corresponding element of array A. I'm assuming there are more fields in B than just z and condition.

Step 1 can be accomplished using ismember, whose second output argument gives the indices into one array matching elements in the other. We're assuming that each element of B has a matching element in A:

[~,I] = ismember({A.condition},{B.condition});

Note that {A.condition} is a cell array with all the condition strings. B(I) is the struct array B reordered to match A.

Step 2 can be accomplished as in rahnema1's answer, which we generalize here to copy multiple fields using a loop over the field names, and using dynamic field names:

fields = fieldnames(B);
fields = setdiff(fields,'condition'); % don't copy over this field
for ii = 1:numel(fields)
   [A.(fields{ii})] = B(I).(fields{ii});
end

Testing:

A(1).condition = 'con1';
A(1).y = [1,1,1];
A(2).condition = 'con2';
A(2).y = [2,2];
A(3).condition = 'con3';
A(3).y = [3,3,3];

B(1).condition = 'con2';
B(1).z = 2;
B(1).x = 'b';
B(2).condition = 'con3';
B(2).z = 3;
B(2).x = 'c';
B(3).condition = 'con1';
B(3).z = 1;
B(3).x = 'a';

[~,I] = ismember({A.condition},{B.condition});

fields = fieldnames(B);
fields = setdiff(fields,'condition'); % don't copy over this field
for ii = 1:numel(fields)
   [A.(fields{ii})] = B(I).(fields{ii});
end

This yields the following struct A:

>> A(1)

ans = 

  struct with fields:

    condition: 'con1'
            y: [1 1 1]
            x: 'a'
            z: 1

>> A(2)

ans = 

  struct with fields:

    condition: 'con2'
            y: [2 2]
            x: 'b'
            z: 2

>> A(3)

ans = 

  struct with fields:

    condition: 'con3'
            y: [3 3 3]
            x: 'c'
            z: 3

Upvotes: 2

rahnema1
rahnema1

Reputation: 15837

You can assign a comma separated list to another comma separated list:

C = A;
[C(2:-1:1).z] = B.z;

or

[C.z] = B(2:-1:1).z;

Upvotes: 1

Florian Drawitsch
Florian Drawitsch

Reputation: 715

Its not elegant, but it should work:

C = A
for idx_A = 1:length(A)
    for idx_B = 1:length(B)
        if strcmp(A(idx_A).condition, B(idx_B).condition)
            C(idx_A).z = B(idx_B).z
        end
    end
end

If you need frequent merging operations, you could consider using tables. If you want to stick to structs, I think it is generally advisable to use structs of arrays instead of arrays of structs, like so:

A.condition = {'con1', 'con2'};
A.y = [rand(10, 1), rand(10, 1)]

That makes many operations easier.

Upvotes: 0

Related Questions