File Exchange

image thumbnail

CATSTRUCT

version 1.3 (4.95 KB) by

Concatenate/merge structures (v4.1, feb 2015).

4.6129
33 Ratings

67 Downloads

Updated

View License

Editor's Note: This file was selected as MATLAB Central Pick of the Week

CATSTRUCT Concatenate or merge structures with different fieldnames
X = CATSTRUCT(S1,S2,S3,...) merges the structures S1, S2, S3 ... into one new structure X. X contains all fields present in the various
structures. An example:

A.name = 'Me' ;
B.income = 99999 ;
X = catstruct(A,B)
% -> X.name = 'Me' ;
% X.income = 99999 ;

If a fieldname is not unique among structures (i.e., a fieldname is present in more than one structure), only the value from the last structure with this field is used. In this case, the fields are alphabetically sorted. A warning is issued as well. An axample:

S1.name = 'Me' ;
S2.age = 20 ; S3.age = 30 ; S4.age = 40 ;
S5.honest = false ;
Y = catstruct(S1,S2,S3,S4,S5) % use value from S4

The inputs can be array of structures. All structures should have the same size. An example:

C(1).bb = 1 ; C(2).bb = 2 ;
D(1).aa = 3 ; D(2).aa = 4 ;
CD = catstruct(C,D) % CD is a 1x2 structure array with fields bb and aa

The last input can be the string 'sorted'. In this case,
CATSTRUCT(S1,S2, ..., 'sorted') will sort the fieldnames alphabetically. To sort the fieldnames of a structure A, you could use CATSTRUCT(A,'sorted') but I recommend ORDERFIELDS for doing that.

When there is nothing to concatenate, the result will be an empty struct (0x0 struct array with no fields).

NOTE
To concatenate similar arrays of structs, you can use simple concatenation:
A = dir('*.mat') ; B = dir('*.m') ; C = [A ; B] ;
Latest version: 4.0 (dec 2013)

Comments and Ratings (46)

CHAO HUANG

Works well for me.

Alan

Alan (view profile)

Jos (10584)

Jos (10584) (view profile)

@arnold Simply use any of the basic concatenation functions, for instance: z = cat(2, x.a, y.a]

If your structures are named like x1, x2, x3, you really need to reconsider your variable naming approach. You would be better off when you stored them in an array of structures:
z.a = cat(x(:).a)

arnold

arnold (view profile)

I understand that it could lead to conflicts but do you have a better idea on how to append data to structures if the data format actually fits?
I have many usage scenarios where it would make sense to use structures for different variables with congruent data format but it then turns out to be quite cumbersome to simply consolidate them. Example: measurement-data

Jos (10584)

Jos (10584) (view profile)

@arnold, that behaviour would be quite arbitrary and depending on the contents of the field which may differ between the two structures. Example:
a.x = 'test', b.x = magic(3), catstruct(a,b) ??

arnold

arnold (view profile)

any chance you can add the ability to append data if fieldnames already exist?

catstruct(a,a) then would result in a new struct with the same fields as a but double the entries in each field.

Gad Molkho

Thanks a lot!

Nugraha

Thank you

Robert

Robert (view profile)

Great function! Greetings from Croatia

David MERCIER

David Young

David Young (view profile)

Anton Semechko

Anton Semechko (view profile)

Solenostomus

Great function, but not versatile enough for what I wanted. I also use structs for configuration settings, but my structs are nested (structs of structs of structs of values).
So I needed to add some recursion:
...
[UFN2,ind2] = unique(FN, 'last') ;
[UFN1,ind1] = unique(FN, 'first') ;

if numel(UFN2) ~= numel(FN),
%warning('catstruct:DuplicatesFound','Fieldnames are not unique between structures.') ;
sorted = 1 ;
end

if sorted,
VAL2 = cell(numel(ind2),1);
for n=1:numel(ind1)
v1 = VAL(ind1(n),:);
v2 = VAL(ind2(n),:);
if iscell(v1) && numel(v1)==1 && isstruct(v1{1}) && iscell(v2) && numel(v2)==1 && isstruct(v2{1})
v = cell(1,1); v{1} = catstruct(v1{1}, v2{1});
VAL2(n) = v;
else
VAL2(n) = v2;
end
end
FN = FN(ind2,:) ;
else
VAL2 = VAL;
end

A = cell2struct(VAL2, FN);
...

This edit only works when 2 structs are combined, which is fine for me (the default config struct and the user defined one), but it's not general because the calls to unique() only identify the first and last occurrence.

This would need more work for the general case and I may have broken some other use cases.

Brad Stiritz

Great utility, thanks for providing this. Please be aware that your current version 4.0 generates two warnings in R2014b that nargchk() will be removed in a future MATLAB version.

Christian

Sam

Sam (view profile)

Colin

Colin (view profile)

Krzysztof

narginchk(1,Inf)
N = nargin ;

sorted = varargin { end };
if ischar(sorted)
narginchk(2,Inf)
assert (all (sorted == 'sorted'))
sorted = 1 ;
N = N-1 ;
else
sorted = 0 ;
end

Jos (10584)

Jos (10584) (view profile)

Thanks Christophe for pointing out the changed behavior of unique! I will fix this soon.

Christophe

One solution to restore compatibility across MATLAB version would be to substitute line 124:
[UFN,ind] = unique(FN) ;
by
if verLessThan('matlab','8.1')
[UFN,ind] = unique(FN) ;
else
[UFN,ind] = unique(FN,'legacy') ;
end

Christophe

This is a very useful function!

Unfortunately, it seems that the release of MATLAB 2013a breaks its behavior. They have changed the behavior of the function unique and now when I type

catstruct(struct('A',1),struct('A',2))

I get

A: 1

Christophe

Jos (10584)

Jos (10584) (view profile)

@Tor Inge Birkenes
I see your problem! An update is being submitted ...

Tor Inge BL

@Jos
If its inteded it is of course all right, but it feels inconsistent to me.
I care about this because I am using structs to store configuration parameters and use your function to merge standard and user defined values. In this case it is expected to have duplicate fields. As you point out this is outside the intended use of the function, but it still works great.

In case my explanation of the problem was insufficient i include concrete example this time:

% Defining struct arrays
s1.name = 'fred';
s1.age = 42;
s1(2).name = 'alice';
s1(2).age = 29;
s2.height = 170;
s2(2).height = 160;
% Concentrate
A=catstruct(s1,s2)
% A is a 1x2 struct array
% with fields: name, age, height

% Add duplicate field
s1(1).height= '142';
s1(2).height = '195';
% Concentrate
A2=catstruct(s1,s2)
% A2 is a struct with fields:
% name, age, height

I is the dimension difference (1x2 vs 1x1) that i find inconsistent, and I did not expect based on the warning "Warning: Duplicate fieldnames found. Last value is used and fields are sorted". I would expect an 1x2 array where the field values from s2 were used when there are conflict, as the function does with the first field in the structure.

Jos (10584)

Jos (10584) (view profile)

@Tor Inge Birkenes
This behaviour is intended. CATSTRUCT merges separate structures containing different(!) field names into 1. For instance, it works perfectly for the initial example given in Loren's blog.

Tor Inge BL

Great function!

I found a bug when concentrating struct arrays with common fields.
Then only the first struct was concentrated, the rest was discarded.
For an example add height-field to s1 in the example here, and concentrate: http://blogs.mathworks.com/loren/2009/10/15/concatenating-structs/#11

I fixed it by changing
VAL = VAL(ind) ;
FN = FN(ind) ;
to
VAL = VAL(ind,:,:) ;
FN = FN(ind,:,:) ;
at line 88-89. Seems to work nicely.

Wintersprite

simple to use, but what I really wanted to do was to gather up 5 structures and put them into one structure variable, whereas this script flattened my existing structures.

Wouter

Wouter (view profile)

I also needed to concatenate arrays and cells at the leaf nodes in the case when the fieldnames match. This can be done by slightly adjusting the code as:
if numel(UFN) ~= numel(FN),
warning('catstruct:DuplicatesFound','Duplicate fieldnames found. Fields are merged and sorted.') ;
Nd = numel(unique(j));
for i=1:Nd
idx = find(j==unique(j(i)));
[nr,nc] = cellfun(@size, VAL(idx));
% comment if fields are not required to be equal in size:
if diff(nr) || diff(nc);error(['Duplicate field ' FN{idx(1)} ' does not have the same size for all structures.']);end
[p,rcm] = min([nc(1) nr(1) 2]);
% concatenate rows as rows, colums as colomns and matrices along 2nd dimension.
VAL{idx(end)} = cat(min(rcm,2),VAL{idx});
end
sorted = 1;
end

Wouter

Wouter (view profile)

grega

grega (view profile)

K E

K E (view profile)

Should be standard in Matlab

This works well. I agree with Jeremy that recursive concatenation would be an advantage. It would also be useful to concatenate arrays and cells at the leaf nodes in the case when the fieldnames match (I've had to do this many times).

But it works for what I'm doing at the moment. Thanks!

great work, may the sun always shine on you.

Jeremy

Jeremy (view profile)

Nice work, just one comment:
I just noticed that when merging two structures, the fact that any dissimilar fields in the FIRST level are merged, but dissimilarities in subsequent levels are lost: e.g.

s1.a = 1;
s1.b = 2;

s2.a = 101;

s3 = catstruct(s1,s2) gives s3.a = 101, s3.b = 2.
But

s1.A.a = 1;
s1.A.b = 2;

s2.A.a = 101;

gives s3.A = a : 101.
i.e. we lose field b. I kind of understand the logic, i.e. that it is now field A that is being overwritten by a new field A, so that a depth=1 merge has occurred, but for anyone looking to do a true merge-style operation, this means needing to loop though each level... any plans to change this?

Daniel

Daniel (view profile)

Jos (the author)

To Benny. Catstruct does work on structures with non-numeric fields (see the example). You're after simple concatenation, for which you do not need catstruct at all ...

a = dir('*.mat')
b = dir('*.txt')
c = [a ; b] ;

Benny .

Good job. It would be very usefull if it also worked on general structures, not only those containing numerical matrices only, e.g.

a = dir('*.txt');
b = dir('*.mat');
c = catstruct(a,b);

Piotr .

Very useful function!

Maziar Hashemi-Nezhad

Perfect basic program
Thanks

Hoi Wong

Cool program. I can't imagine why MATLAB wouldn't have something like this built-in, but you filled the gap!

Jos (the author)

new version 2.0 (sep 2007). A bug was removed which ocured when fields contained cell arrays.

Jingzhao Ou

A very useful script!

Nadaraja Pillai

Hi ,
It is possible to concates the structure simply using collon... For example...

X=[S1:S2:S3....]

It will do it...

Paolo de Leva

This function is a must for people using structures. I wonder why MATLAB doesn't allow (as far as I know) structure concatenation using the [S1, S2] syntax, when the two structures have different fields. The builtin function STRUCT can't add fields to an existing structure...

Updates

1.3

changed number of input arguments check to accommodate future releases. Probably this function will no longer work anymore in older releases :-(

1.2

Due to changes implemented by The Mathworks in their set functions, I had to explicitly tell unique to return the last occurrence of a value in a set ...

1.1

now deals correctly with arrays of structures (thanks to Tor Inge Birkenes for pointing out this problem)

added identifiers

add keywords

removed bug when delaing with fields containing cell arrays

added semi-colons

MATLAB Release
MATLAB 8.3 (R2014a)
Acknowledgements

Inspired: Lynx MATLAB Toolbox, Structable

Download apps, toolboxes, and other File Exchange content using Add-On Explorer in MATLAB.

» Watch video