How do I merge two structs, where one field in the struct is itself a struct?

18 visualizzazioni (ultimi 30 giorni)
I'm trying to merge two structs. Both structs have many matching fields, but both structs also have one or more fields that are not in the other struct.
When I've needed to do this in the past, I've converted the structs to tables, using struct2table, then used outerjoin to merge the tables, then converted the merged table back to a struct with table2struct.
However, in this case, one of the fields in the struct is itself a struct. This is causing outerjoin to error.
I thought about "brute-forcing" things by taking the field that's a struct out, using my standard process (i.e. struct2table, outerjoin, table2struct), then merging the problem field back in. However, it's not obvious to me that this would always work. Plus, I'd like a more "elegant" solution, if one is available.
Below is a "toy" example showing what I'm trying to do, both the current case (with a struct field) and the more typical case (without a struct field). Toggle the useStructVar Boolean variable to see the two situations.
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
index = 1;
for s = 1:length(obj.Space)
for meas = obj.Space(s).Measurement
if isempty(obj.Experiment.Measurement)
obj.Experiment.Measurement = meas;
else
try
obj.Experiment.Measurement(index) = meas;
catch ME
switch ME.identifier
% This case catches the
% 'Subscripted assignment betweeen dissimilar structures'
% error.
case 'MATLAB:heterogeneousStrucAssignment'
currentMeasurementTable = struct2table(obj.Experiment.Measurement,'AsArray',true);
addedMeasurementTable = struct2table(meas, 'AsArray', true);
updatedMeasurementTable = outerjoin(currentMeasurementTable,...
addedMeasurementTable,...
'MergeKeys', true);
obj.Experiment.Measurement = table2struct(updatedMeasurementTable);
disp(''); % DEBUGGING
otherwise
rethrow(ME)
end % End "switch ME.identifier"
end % End "try/catch" block
end % End "if isempty(obj.Experiment.Measurement)
index = index + 1;
end % End "for meas = obj.Space(s).Measurement"
end % End "for s = 1:length(obj.Space)"
% At the end, if useStructVar = true, I get the following error:
%
% Error using tabular/outerjoin (line 141)
% Error when finding unique values for left and right key variables 'Device' and 'Device':
% Undefined function 'sort' for input arguments of type 'struct'..
%
% Error in MY_MATLAB_DEMO_MFILE (line 80)
% updatedMeasurementTable = outerjoin(currentMeasurementTable,...
%
%==========================================================================
%
% If useStructVar = false, then obj.Experiment.Measurement "looks like":
% MeasurementId Layer Device numberDevices
% 'Measurement01' 'ABC' 'nonStructVar01' []
% 'Measurement02' 'XYZ' 'nonStructVar01' []
% 'Measurement03' 'DEF' 'nonStructVar02' '1'
% 'Measurement04' 'UVW' 'nonStructVar02' '1'
% 'Measurement05' 'XYZ' 'nonStructVar02' []
% which is what I expect.

Risposta accettata

Stephen23
Stephen23 il 14 Nov 2024
You can always use a loop and DIY:
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
index = 1;
for s = 1:length(obj.Space)
for meas = obj.Space(s).Measurement
if isempty(obj.Experiment.Measurement)
obj.Experiment.Measurement = meas;
else
try
obj.Experiment.Measurement(index) = meas;
catch ME
switch ME.identifier
% This case catches the
% 'Subscripted assignment betweeen dissimilar structures'
% error.
case 'MATLAB:heterogeneousStrucAssignment'
fld = fieldnames(meas);
for k = 1:numel(fld)
obj.Experiment.Measurement(index).(fld{k}) = meas.(fld{k});
end
otherwise
rethrow(ME)
end % End "switch ME.identifier"
end % End "try/catch" block
end % End "if isempty(obj.Experiment.Measurement)
index = index + 1;
end % End "for meas = obj.Space(s).Measurement"
end % End "for s = 1:length(obj.Space)"
% At the end, if useStructVar = true, I get the following error:
%
% Error using tabular/outerjoin (line 141)
% Error when finding unique values for left and right key variables 'Device' and 'Device':
% Undefined function 'sort' for input arguments of type 'struct'..
%
% Error in MY_MATLAB_DEMO_MFILE (line 80)
% updatedMeasurementTable = outerjoin(currentMeasurementTable,...
%
%==========================================================================
%
% If useStructVar = false, then obj.Experiment.Measurement "looks like":
% MeasurementId Layer Device numberDevices
% 'Measurement01' 'ABC' 'nonStructVar01' []
% 'Measurement02' 'XYZ' 'nonStructVar01' []
% 'Measurement03' 'DEF' 'nonStructVar02' '1'
% 'Measurement04' 'UVW' 'nonStructVar02' '1'
% 'Measurement05' 'XYZ' 'nonStructVar02' []
% which is what I expect.
obj.Experiment.Measurement
ans = 1x5 struct array with fields:
MeasurementId Layer Device NumberDevices
obj.Experiment.Measurement.Device
ans = struct with fields:
Type: 'Type01' Material: 'Material01' Layer: 'Layer01'
ans = struct with fields:
Type: 'Type01' Material: 'Material01' Layer: 'Layer01'
ans = struct with fields:
Type: 'Type02' Material: 'Material02' Layer: 'Layer02'
ans = struct with fields:
Type: 'Type02' Material: 'Material02' Layer: 'Layer02'
ans = struct with fields:
Type: 'Type02' Material: 'Material02' Layer: 'Layer02'
  2 Commenti
Bob
Bob il 18 Nov 2024
Thanks for the suggestion! I'm going to try coding up both your suggestion and Voss's suggestion, both to better understand how they work and to see which one would work best for my use case.
Once I've done that, I'll come back and accept one of the answers.
Bob
Bob il 27 Nov 2024 alle 14:56
I believe either proposed solution would work but this one is easier to integrate into the existing code, so I'm accepting it.

Accedi per commentare.

Più risposte (1)

Voss
Voss il 14 Nov 2024
Setting up obj like you have (with clear obj at the top so that it doesn't contain anything left over from previous runs):
clear obj
obj.Experiment = struct('Measurement',struct([]));
useStructVar = true; % true = "Real-world" case, false = Debugging case
nonStructVar01 = 'nonStructVar01';
nonStructVar02 = 'nonStructVar02';
structVar01 = struct('Type', 'Type01',...
'Material', 'Material01',...
'Layer', 'Layer01');
structVar02 = struct('Type', 'Type02',...
'Material', 'Material02',...
'Layer', 'Layer02');
if useStructVar
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', structVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', structVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', structVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', structVar02);
else
obj.Space(1).Measurement(1) = struct(...
'MeasurementId','Measurement01',...
'Layer', 'ABC',...
'Device', nonStructVar01);
obj.Space(1).Measurement(2) = struct(...
'MeasurementId','Measurement02',...
'Layer', 'XYZ',...
'Device', nonStructVar01);
obj.Space(2).Measurement(1) = struct(...
'MeasurementId','Measurement03',...
'Layer', 'DEF',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(2) = struct(...
'MeasurementId','Measurement04',...
'Layer', 'UVW',...
'NumberDevices', '1',...
'Device', nonStructVar02);
obj.Space(2).Measurement(3) = struct(...
'MeasurementId','Measurement05',...
'Layer', 'XYZ',...
'NumberDevices', '',...
'Device', nonStructVar02);
end
One approach that may work for your purposes is to standardize the Measurement struct in each element of obj.Space so that they all contain the same set of fields. (Actually, not in obj.Space itself but a temporary variable S, which is initially equal to obj.Space, so as not to modify obj.Space, in case that's a concern.) This requires supplying values for any missing fields. (In the example below I'm using empty numeric arrays [] for those missing values, but you can use something else if it matters, possibly different values depending on the name and/or class of the field.) Then obj.Experiment.Measurement can easily be constructed by concatenating all the Measurement structs together.
% temporary variable S, so as not to change obj.Space
S = obj.Space;
% set of field names encountered so far
F = {};
for ii = 1:numel(S)
% fieldnames of current S(ii).Measurement
Fii = fieldnames(S(ii).Measurement);
% augment S(ii).Measurement with [] for any fields that have been
% encountered previously but that S(ii).Measurement lacks
Fnew = F(~ismember(F,Fii));
for kk = 1:numel(Fnew)
[S(ii).Measurement.(Fnew{kk})] = deal([]);
end
% augment previous S(jj).Measurement (jj = 1,...,ii-1) with [] for any
% fields that exist in S(ii).Measurement but that haven't been
% encountered previously
Fnew = Fii(~ismember(Fii,F));
for kk = 1:numel(Fnew)
for jj = 1:ii-1
[S(jj).Measurement.(Fnew{kk})] = deal([]);
end
end
% append new field names to the set of encountered field names F
F = [F; Fnew]; %#ok<AGROW>
% now each S(jj).Measurement (jj = 1,..,ii-1) has the same set of
% fields, which is F
% continue on to the next S(ii) ...
end
% now each S(ii).Measurement has the same set of fields, so (assuming their
% sizes are compatible) they are able to be concatenated
obj.Experiment.Measurement = [S.Measurement];
Check the result:
% convert to table to check the result
disp(struct2table(obj.Experiment.Measurement))
MeasurementId Layer Device NumberDevices _________________ _______ __________ _____________ {'Measurement01'} {'ABC'} 1x1 struct {0x0 double} {'Measurement02'} {'XYZ'} 1x1 struct {0x0 double} {'Measurement03'} {'DEF'} 1x1 struct {'1' } {'Measurement04'} {'UVW'} 1x1 struct {'1' } {'Measurement05'} {'XYZ'} 1x1 struct {0x0 char }
  1 Commento
Bob
Bob il 18 Nov 2024
Thanks for the suggestion! I'm going to try coding up both your suggestion and Stephen23's suggestion, both to better understand how they work and to see which one would work best for my use case.
Once I've done that, I'll come back and accept one of the answers.

Accedi per commentare.

Categorie

Scopri di più su Programming in Help Center e File Exchange

Prodotti


Release

R2018a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by