Get a subset of a structure array in mex

21 views (last 30 days)
Jim Hokanson
Jim Hokanson on 9 Dec 2016
Edited: James Tursa on 19 Dec 2016
Is it possible to get a subset of a structure array in mex? In other words, the mex equivalent of the following in Matlab:
s2 = s1(2:5); %s1 is a structure array
In my (current) use case, the structure array is empty, although fields have been defined.
  3 Comments
Jim Hokanson
Jim Hokanson on 9 Dec 2016
I think the best approach will probably be to have an empty structure (with fields) and to duplicate and resize the array.
I'll post some code at some point. I think the important part to know is that a structure array has its "data" as an array of mxArrays with the size equal to => (# of fields)*(# of elements of array) (based on the link above).

Sign in to comment.

Accepted Answer

Jim Hokanson
Jim Hokanson on 19 Dec 2016
Edited: Jim Hokanson on 19 Dec 2016
This is the code I ended up using. At the end of the day I essentially wanted to have an object "template" (i.e. field names defined) that I then populate with values at the appropriate time.
Prior to the example code below, I've initialized empty "template" objects into a cell array, 1 for each unique type of object that I've encountered.
//- This is specific to my code, but ref_object is an empty structure
// BUT with fields, like in Matlab => s = struct('a',{},'b',{},'c',{})
int object_id = data.object_ids[object_data_index];
mxArray *ref_object = mxGetCell(data.objects,object_id);
//- Create a fresh structure/structure array from the reference
//- Copies the field names
mxArray *return_obj = mxDuplicateArray(ref_object);
//- I know the # of fields, this is a bit of a note to my self
// in that I'm not sure which of these will execute faster
//- "data" is a C structure
//Not sure which is better ...
//int n_fields = data.child_count_object[object_data_index];
int n_fields = mxGetNumberOfFields(return_obj);
//- This is the magic that I think I pulled from one of James Tursa's other
// posts (and this one too). The important part is that the "data" (in mxGetData, mxSetData)
// for a structure or structure array is a linear array of pointers to mxArrays.
mxArray **object_data = mxCalloc(n_fields*n_objects,sizeof(mxArray*));
mxSetData(return_obj,object_data);
mxSetN(return_obj,n_objects);
return return_obj;
  1 Comment
James Tursa
James Tursa on 19 Dec 2016
Looks good, but depending on how you are actually building your reference empty structures, you might need to add the following also:
mxSetM(return_obj,1);
I.e., there may be a 0 in the M spot that you need to set to 1.
Regarding the methods used to get n_fields, the function mxGetNumberOfFields simply peeks at a location behind the pi pointer where the number of fields is stored as an int. So there is no counting involved. You only incur the function call overhead and a simple int pointer dereference. (The field names themselves are actually stored behind the pi pointer as well).

Sign in to comment.

More Answers (2)

James Tursa
James Tursa on 12 Dec 2016
Edited: James Tursa on 13 Dec 2016
Here is some sample code to create a subset of a struct array inside a mex routine. One key point is that there are no official API functions that allow you to do this the same way that MATLAB does it at the m-file level. At the m-file level, MATLAB creates reference copies (a type of shared copy) of all of the field elements. This only involves incrementing a single counter inside the mxArray itself ... no deep data copy or even a new mxArray struct header is needed, so very efficient. The only thing available to you officially at the mex level is mxDuplicateArray, which could be a really nasty memory/performance hit. Hence this is one case where I would highly advise going outside the official API and using the undocumented API function mxCreateReference. This will allow you to efficiently construct the subset array exactly the same way MATLAB does at the m-file level. Code is listed below with a simple driver routine. You may want to modify how the code behaves in the case of invalid index inputs ... I just left that element NULL but you might want to generate an error.
/* Sample code to create subset of a struct array
*
* B = CreateStructSubset( S, x )
*
* S = a struct array
* x = a vector of indexes
* B = S(x)
*
* This mex routine does the equivalent of the B = S(x) statement at the
* m-code level. It uses the undocumented API function mxCreateReference
* to create reference copies of the "copied" contents rather than deep
* copies (just like MATLAB would do at the m-file level).
*
* Programmer: James Tursa
* Date: 12-Dec-2016
*
*/
/* Includes ----------------------------------------------------------- */
#include "mex.h"
/* Prototypes --------------------------------------------------------- */
mxArray *mxCreateReference(mxArray *); /* Undocumented API function */
mxArray *mxCreateStructSubset(const mxArray *mx, mwSize n, int *elements);
/* Gateway ------------------------------------------------------------ */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
mxArray *mx_elements;
int i, n;
int *elements;
/* Example driver code. 1st input must be a struct. 2nd input must be a
* numeric variable, which is turned into an int32 just to get an equivalent
* int array to pass into the subset creation routine */
if( nrhs == 2 && mxIsStruct(prhs[0]) && mxIsNumeric(prhs[1]) ) {
mexCallMATLAB( 1, &mx_elements, 1, prhs+1, "int32" );
n = mxGetNumberOfElements(mx_elements);
elements = (int *) mxGetData(mx_elements);
for( i=0; i<n; i++ ) {
elements[i]--; /* Turn 1-based MATLAB indexing into 0-based C indexing */
}
plhs[0] = mxCreateStructSubset( prhs[0], n, elements );
mxDestroyArray(mx_elements);
} else {
mexErrMsgTxt("Invalid inputs");
}
}
/* Function to create a subset from an input struct. If an element value
* is outside the range of the dimensions of the input struct, that
* particular element is left empty and no copy is made. Could of course
* change this behavior to whatever you want instead (e.g., error).
* The elements array is assumed to be 0-based C indexing. */
mxArray *mxCreateStructSubset(const mxArray *mx, mwSize n, int *elements)
{
mxArray *result = NULL;
mwSize i, j, k, nelements, nfields, M, N;
mxArray **mdata, **rdata, **data;
const char **fieldnames;
if( mx && mxIsStruct(mx) ) { /* Make sure we have a struct to work with */
nfields = mxGetNumberOfFields(mx);
nelements = mxGetNumberOfElements(mx);
fieldnames = (char **) mxMalloc(nfields * sizeof(*fieldnames));
for( i=0; i<nfields; i++ ) {
fieldnames[i] = mxGetFieldNameByNumber(mx,i); /* Copy the fieldname pointers */
}
if( mxGetNumberOfDimensions(mx) == 2 && mxGetM(mx) == 1 ) {
M = 1; N = n;
} else {
M = n; N = 1;
}
result = mxCreateStructMatrix(M, N, nfields, fieldnames);
mxFree(fieldnames);
mdata = (mxArray **) mxGetData(mx);
rdata = (mxArray **) mxGetData(result);
for( i=0; i<n; i++ ) {
k = elements[i];
if( k >= 0 && k < nelements ) {
data = mdata + k * nfields;
for( j=0; j<nfields; j++ ) { /* For each element, copy all the fields */
if( *data ) {
*rdata++ = mxCreateReference(*data++); /* Make a reference copy, not a deep copy */
} else {
rdata++; data++; /* *data is NULL, so nothing to copy */
}
}
} else {
rdata += nfields; /* k is out of range, so leave all fields NULL */
}
}
}
return result;
}
  1 Comment
Jan
Jan on 16 Dec 2016
@James: Keep care. An angry armadillo has entered your room and types on your keyboard while you are away.

Sign in to comment.


Jan
Jan on 9 Dec 2016
You have to create a new struct array at first by mxCreateStructMatrix. You need the number of elements and number of fields as well as the field names in a char** obtained from the original struct. Then you copy the wanted fields in a loop from the old to the new struct. The code is not tricky, but tedious to type.
An excellent example for the beauty of Matlab! While s2 = s1(2:5) is simply nice, the C code looks like somebody had rolled an angry armadillo over the keyboard.
  1 Comment
Jim Hokanson
Jim Hokanson on 9 Dec 2016
Yuck, angry armadillo indeed :) Perhaps I need to change my approach. I wonder if mexCallMatlab is able to do perform better (behind the scenes optimizations), although I was hoping for something a bit more elegant.

Sign in to comment.

Tags

Community Treasure Hunt

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

Start Hunting!