Pick a struct element by field name and some non-sequential index

I mean to use a struct to hold a "table":

% Sample data
% idx  idxstr  var1  var2  var3
%   1    i01    3.5  21.0   5
%  12    i12    6.5   1.0   3

The first row contains the field names. Assume I created a struct

ds2 = struct( ...
    'idx', { 1, 12 }, ...
    'idxstr', { 'i01', 'i12' }, ...
    'var1', { 3.5, 6.5 }, ...
    'var2', { 21, 1 }, ...
    'var3', { 5, 3 } ...
);

How can I retrieve the value for field var2, for the row corresponding to idxstr equal to 'i01'?

Notes:

  1. I cannot ensure the length of idxstr elements will always be 3.
  2. Ideally, I would have a method that also works for columns var2 containing strings, or any other type of variable.

PS: I think https://stackoverflow.com/a/35976320/2707864 can help.

Upvotes: 1

Views: 419

Answers (4)

Tasos Papastylianou
Tasos Papastylianou

Reputation: 22215

As I mentioned in the comments, I believe you have the wrong kind of struct for this work. Instead of an array of (effectively single-row) structs, you should instead have a single struct with 'array' fields. (numeric or cell, as appropriate).

E.g.

d = struct(
   'idx', [1, 12 ],
   'idxstr', {{'i01', 'i12'}},
   'var1', [3.5, 6.5],
   'var2', [21, 1],
   'var3', [5, 3]
);

With this structure, your problem becomes infinitely easier to deal with:

d.var2( strcmp( 'i01', d.idxstr ) )
% ans = 21

This is also far more comparable to R / pandas dataframes functionality (which are also effectively initialised via names and equally-sized arrays like this).


PS. Note carefully the syntax used for the 'idxstr' field: there is an 'outer' cell array with a single element, meaning you're only creating a single struct, rather than an array of structs. This single element happens to be a cell array of strings, where this cell array is of the same size (i.e. has the same number of 'rows') as the numeric arrays.

UPDATE

In response to the comment, adding 'rows' should be fairly straightforward. Here is one approach:

function S = addrow( S, R )
    FieldNames = fieldnames( S ).';   NumFields  = length( FieldNames );
    for i = 1 : NumFields,
        S.( FieldNames{i} ) = horzcat( S.( FieldNames{i} ), R{i} );
    end
end

Then you can simply do:

d = addrow( d, {5, 'i011', 2.7, 10, 11} );

Upvotes: 3

ThomasIsCoding
ThomasIsCoding

Reputation: 101064

Maybe you can try the code like below using strcmp

>> [ds2.var2](strcmp('i01',{ds2.idxstr}))
ans = 21

Upvotes: 2

I put together function

function el = struct_pick(s, cdata, cnames, rname)
    % Pick an element from a struct by column and row name
    coldata = vertcat(s.(cdata));
    colnames = mat2cell(vertcat(s.(cnames)), ones(1, length(s)));
    % This assumes rname is a string
    flt = strcmp(colnames, rname);
    el = coldata(logical(flt));
endfunction

which is called with

% Pick an element by column and row name
cdata = 'var3';
cnames = 'idxstr';
rname = 'i01';
elem = struct_pick(ds2, cdata, cnames, rname);

and it seems to do the job. I don't know if it is an unnecessarily contrived way of doing it.

Still have to deal with the possibility that the row names are not strings, as with

cnames = 'idx';
rname = 1;

EDIT: If the strings in idxstr are not all of the same length, this throws error: vertcat: cat: dimension mismatch. The answer by Ander Biguri can handle this case.

Upvotes: 1

Ander Biguri
Ander Biguri

Reputation: 35525

Assuming that idxstr can be more than 3 characters (there is a shorter version of its always 3 chars), this is the thing I came up with (tested on MATLAB):

logical_index=~cellfun(@isempty,strfind({ds2(:).idxstr},'i01'))

you can access the variables as:

ds2(~cellfun(@isempty,strfind({ds2(:).idxstr},'i01'))).var2;
% using above variable
ds2(logical_index).var2;

You can understand now why MATLAB introduced tables hehe.

Upvotes: 2

Related Questions