Loren on the Art of MATLAB

November 24th, 2006

Working with Arrays of Structures

Though I have covered this topic somewhat in the past, it seems like a good time to refresh the information. There are recent posts on the MATLAB newsgroup relating to this topic such as this one.

Contents

Original Question

Suppose I have a structure array and want to find all the entries with a value of 4, without looping, something like [m,n] = find(f.h == 4).

f(1).h = [1 2 3 4];
f(2).h = [5 6 7 8];
try
 [m,n] = find(f.h == 4);
end

Why can't I use the find statement directly? Let's take a look at the error message to understand.

lerr = lasterror;
disp(lerr.message)
Error using ==> eq
Too many input arguments.

Too many input arguments? What is f.h? For that matter, what exactly is f again?

f
f = 
1x2 struct array with fields:
    h

f is a struct array, and f.h is a comma-separated list.

f.h
ans =
     1     2     3     4
ans =
     5     6     7     8

Alternatives

To turn this list into a MATLAB construct I can use, I'd normally either wrap it inside [] or {}. If I wrap f.h inside [], I lose the information about what is in the first element of f and what is in the second.

[f.h]
ans =
     1     2     3     4     5     6     7     8

Wrapping f.h inside {}, I have a cell array to work with.

{f.h}
ans = 
    [1x4 double]    [1x4 double]

I still can't immediately use find or numeric functions on this array.

try
    [m,n] = find({f.h} == 4);
end
lerr = lasterror;
disp(lerr.message)
Error using ==> evalin
Undefined function or method 'eq' for input arguments of type 'cell'.

Solution

What I'd like is a way to work with my struct, without writing too much code, without looping, that is ideally a pattern I can reuse as my problem evolves. This is exactly what arrayfun was designed to help with. It works on each element of an array, and I need to just tell it what I want to operate on one element, as well as telling arrayfun what array to work on.

Let's first find the values in the struct array f equal to 4. Since I have 2 arrays embedded in f, and they may each have different numbers of outputs, I have to clearly state that the outputs need to go into a cell array.

[m,n] = arrayfun(@(x)find(x.h==4),f,'uniformoutput',false)
m = 
    [1]    [1x0 double]
n = 
    [4]    [1x0 double]

This becomes even more obvious if I can another array, g that is even less "regular" than f.

g = f;
g(3).h = [1 2 17 4];
g(4).h = [1 3 17 5 9 17];
[mg,ng] = arrayfun(@(x)find(x.h==17),g,'uniformoutput',false)
mg = 
    [1x0 double]    [1x0 double]    [1]    [1x2 double]
ng = 
    [1x0 double]    [1x0 double]    [3]    [1x2 double]

Some problems are more benign however and it would be wasteful to return results in a cell array and then have to unpack them into a numeric array, for example, the function max, which generally has a single value as the result.

[minval,idx] = arrayfun(@(x)max(x.h),f)
[minval,idx] = arrayfun(@(x)max(x.h),g)
minval =
     4     8
idx =
     4     4
minval =
     4     8    17    17
idx =
     4     4     3     3

Related Topics

Here are some links to related blogs and MATLAB reference pages.

Your Thoughts

  • Do you use struct arrays?
  • If yes, do you use arrayfun, or do you use loops? Whichever your choice is, can you say more about why it's your choice?
  • Do you avoid struct arrays all together and use something else? If so, what data representations do you use instead?

Let's see your feedback here.


Get the MATLAB code

Published with MATLAB® 7.3

8 Responses to “Working with Arrays of Structures”

  1. Jessee replied on :

    In general, is arrayfun faster than looping? It seems to me that arrayfun would have to perform a loop anyway.

  2. Loren replied on :

    Jessee-

    arrayfun can be much faster than looping. First, results are automatically preallocated in size. Second, the loop inside means that MATLAB doesn’t reinterpret the statement each time and pieces of the code can’t have side effects that change results for the rest of the array. arrayfun can take advantage of this in ways that it would be hard or impossible to do in a for loop.

    –Loren

  3. Dan K replied on :

    Loren,
    Actually the area where I tend to run into the most problems is in constructing my structure array. I have a situation where I get back a collection of possible options (e.g.
    S.filtSize=[10,20,30];S.name={’john’,'george’};
    S.type={@hann};

    I spent quite a while trying to find a non loop method for creating a structure array with all possible combinations of settings (taking one from each category)
    example: S(1)=struct(’filtSize’,10,’name’,john’,'type’,@hann);
    S(2)=struct(’filtSize’,20,’name’,john’,'type’,@hann);…

    This is a simple example but in the where creating the struct array from a cell array would work, but in actuality I have some 10 fields with any number of entries of various types in each field. I’ve been looking at this post trying to find a way to use the techniques you describe to address this without any luck… Any thoughts?

  4. reza replied on :

    how about:

    [m,n] = find(cat(1,f.h) == 4)

    cat(1,f.h) creates an array by concatinating the two lists produced by f.h on the first axis (vertically).

  5. Loren replied on :

    Reza-

    That works fine if all the original arrays are the same length. But that’s not true for the extended example with g.

    Then I get this error message:

    ??? Error using ==> cat
    CAT arguments dimensions are not consistent.
    

    –Loren

  6. Loren replied on :

    Dan-

    I am not sure that I completely understand your problem. But here’s an idea to start you off. Try using ndgrid to get indices for your various fields.

    S.filtSize=[10,20,30];
    S.name={’john’,'george’};
    S.type={@hann};
    [filtInd, nameInd, typeInd] = ndgrid(length(S.filtSize),length(S.name),length(S.type);
    
    filtInd =
         1     1
         2     2
         3     3
    nameInd =
         1     2
         1     2
         1     2
    typeInd =
         1     1
         1     1
         1     1
    

    Using triplets of those indices gets you all the combinations of inputs.

    –Loren

  7. Oliver A. Chapman, PE replied on :

    Loren,

    This column introduced me to the “arrayfun,” et al. and helped me see a potential use for function handles. After reviewing the documentation, I can follow part of what’s going on.

    At the end of your first example, we have two cell array variables, m & n. The entire purpose of this batch of code is to generate the indices that will allow subsequent code to find the values indicated. Based on your example structure arrays, I expect the output for the first example to be something like:

    1, 4

    And, for the second:

    1, 3
    2, 3
    2, 6

    But, when I run your examples, I don’t know where to find these indices. I mean, I’ve poked around in these output cell arrays and I can find the values I know should be someplace. But I don’t understand why they ended up where they are and why some of the cells are empty.

    Maybe you can add a bit more detail about what is happening. Maybe you can write a batch of code using for loops that produces the same results so we can see the data flow.

    However, even if I did understand all the details, I’m not sure if I’d ever use this technique. All of us here who looked at this don’t think our coworkers would follow what is going on.

  8. Loren replied on :

    Oliver-

    Here’s the equivalent for-loop if f is a vector:

    for i=1:numel(f)
       [ml{i} nl{i}] = find(f(i).h == 4)
    end
    
    isequal(m,ml)
    isequal(n,nl)
    

    –Loren

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Loren Shure works on design of the MATLAB language at The MathWorks. She writes here about once a week on MATLAB programming and related topics.

  • Jun: I totally can not believe it, Loren. You are really helpful. Thank you so much, MATLAB master!
  • Loren: Wow folks- Always lots of interest when there’s a quickie to try out! I will only make 2 general...
  • Loren: Jun- ismember is your friend here: >> [aa,ind] = ismember(Array2,Arra y1) aa = 1 1 1 1 1 1 1 ind = 1 2 1 4 4 3...
  • Dan: I like the first way better than the second way. Combining the arrays into one and running any is nice, although...
  • James Myatt: How about I = (a == 0 | b == 0); a(I) = []; b(I) = [];
  • Tunc: Hello Loren, love your blog because of such inspiring and challenging comments to such ’small’...
  • Pekka Kumpulainen: Here is my tradeoff. I usually want to keep the original variables as they are most probably...
  • Iain: Followup: Of course, to allow NaNs (counting them as non-zero): mask = (a~=0) & (b~=0); The mask says “a...
  • Matt Fig: I would usually go with something like this: y = a&b; x = a(y); y = b(y); But I was surprised to find...
  • kk: c=all([a;b]) a(c) a(b)

These postings are the author's and don't necessarily represent the opinions of The MathWorks.