MATLAB Array of structures assignment - arrays

I have an array of structures. Lets say
s(1).value, ... , s(5).value.
I have a vector of values, lets say vals = [1 2 3 4 5], that i want to assign to the array of structures. So written in pseudocode i want: s(:).value = vals.
As shown below there is a know solution. But is it really not possible to do this assignment in 1 line as in the pseudocode?
% Vector of values
vals = [1 2 3 4 5];
n = length(vals);
% Initialize struct
s(n).values = 0;
% Put vals into my struct.values
[s(1:n).values] = ???
% Known solution that i am not satisfied with:
vals_c = num2cell(vals);
[s(1:n).values] = vals_c{:};
Best regards, Jonas

It's possible to do this in one line using cell2struct in conjuction with num2cell.
% Vector of values
vals = [1 2 3 4 5];
n = length(vals);
% Put vals into my struct.values
s = cell2struct(num2cell(vals), 'values', 1)
% transpose if orientation is important
s = s.';
it's not pretty, but it does do it in one line. cell2struct supports multiple entries so you may be able to populate many fields.
The big downside is that it creates the struct from scratch, so you'd have to do a struct merge if you need to add this data to an existing struct.

Having recently gone through the same phase I thought I'd answer this one.
To create a new structure with one field:
field = 'f';
value = {'some text';
[10, 20, 30];
magic(5)};
s = struct(field,value)
Create a nonscalar structure with several fields:
field1 = 'f1'; value1 = zeros(1,10);
field2 = 'f2'; value2 = {'a', 'b'};
field3 = 'f3'; value3 = {pi, pi.^2};
field4 = 'f4'; value4 = {'fourth'};
s = struct(field1,value1,field2,value2,field3,value3,field4,value4)
Also, as I'd always suggest, going over the documentation a few times is quite necessary and useful, so there you go. https://in.mathworks.com/help/matlab/ref/struct.html

Related

How to convert strings in a cell array to numerical stand-in values?

In Matlab, I have a cell array of cell arrays created using textscan on a tab-separated data file. Some columns of the data set are strings. Here is an example array, 'data':
data{1,1} = {1; 2; 3; 4; 5};
data{1,2} = {11; 12; 13; 14; 15};
data{1,3} = {'Ringo'; 'Paul'; 'The Beatles'; 'John'; 'George'};
I want to convert the string columns to specific numerical stand-in values (i.e. 'The Beatles' = 1, 'John' = 2, 'Paul' = 3, 'George' = 4, 'Ringo' = 5). Also, it may be important to note that some cells contain more than one "word".
For some context, the entire data set will ultimately be converted from a cell array into one neatly packaged double array (data = cell2mat(data)) for easier manipulation.
The only solution I know is to loop through the string array and use a switch statement:
vec_0 = data{1,3};
for ii = 1:length(vec_0)
switch vec_0{ii}
case 'The Beatles'
vec_f{ii,1} = 1;
case 'John'
vec_f{ii,1} = 2;
case 'Paul'
vec_f{ii,1} = 3;
case 'George'
vec_f{ii,1} = 4;
case 'Ringo'
vec_f{ii,1} = 5;
end
end
% Replace string column with numerical stand-ins
data{1,3} = vec_f;
% Convert cell array to double array
data = cell2mat(data);
Now we have:
data = [1 11 5; 2 12 3; 3 13 1; 4 14 2; 5 15 4];
What is a more optimal way to do this?
If you have a specific mapping of strings to numeric values...
Let's say you have a mapping of strings to numeric values as defined in the following 5-by-2 cell array (one mapping per row):
numMap = {'The Beatles', 1; 'John' , 2; 'Paul' , 3; 'George' , 4; 'Ringo' , 5};
Then you can use ismember to convert the strings to their mapped numeric values and save it back in data like so:
[~, index] = ismember(data{1, 3}, numMap(:, 1));
data{1, 3} = numMap(index, 2);
If you need to generate a mapping of strings to numeric values...
You can generate an array of numerical stand-ins with unique, convert that to a cell array with num2cell, and save it back in data like so:
[uniqueStrings, ~, numIndex] = unique(data{1, 3}, 'stable');
data{1, 3} = num2cell(numIndex);
And uniqueStrings will contain the unique multi-word strings from data{1, 3}.
Converting to a double array:
Once you've chosen one of the above options, you can then convert your sample data to a 5-by-3 double array like so:
data = cell2mat([data{:}]);
data =
1 11 5
2 12 3
3 13 1
4 14 2
5 15 4

Reshaping nested struct arrays to cell array having elements with different sizes

I have a similar question to my previous one. This time the form of the nested structure looks like this:
Sizes = [2, 5, 8, 6, 3];
cells = 5;
for i = 1:cells
for j = 1:Sizes(i)
a(i).b.c(j).d = rand(1,1);
end
a(i).b.Size = Sizes(i);
end
Again I would like to put all the d values of a(:).b.c(:) into a single cell array that contains 1 x cells cells.
Here is my solution using cellfun but I would like to avoid this function:
ab = [a.b];
abc = {ab.c};
abcd = cellfun(#(x) [x.d], abc, 'UniformOutput', false);
Using the previous solution for abc:
abc = [ab.c];
creates a 1x24 struct array with field d. I thought of using the Size field to reshape this result into a cell array but I don't know how or if it is possible. Do you have a better appraoch without using loops and without cellfun?
You can do this using mat2cell as follows:
ab = [a.b];
abc = [ab.c];
abcd = mat2cell([abc.d], 1, [ab.Size]);

Given two arrays A and B, how to get B values which are the closest to A

Suppose I have two arrays ordered in an ascending order, i.e.:
A = [1 5 7], B = [1 2 3 6 9 10]
I would like to create from B a new vector B', which contains only the closest values to A values (one for each).
I also need the indexes. So, in my example I would like to get:
B' = [1 6 9], Idx = [1 4 5]
Note that the third value is 9. Indeed 6 is closer to 7 but it is already 'taken' since it is close to 4.
Any idea for a suitable code?
Note: my true arrays are much larger and contain real (not int) values
Also, it is given that B is longer then A
Thanks!
Assuming you want to minimize the overall discrepancies between elements of A and matched elements in B, the problem can be written as an assignment problem of assigning to every row (element of A) a column (element of B) given a cost matrix C. The Hungarian (or Munkres') algorithm solves the assignment problem.
I assume that you want to minimize cumulative squared distance between A and matched elements in B, and use the function [assignment,cost] = munkres(costMat) by Yi Cao from https://www.mathworks.com/matlabcentral/fileexchange/20652-hungarian-algorithm-for-linear-assignment-problems--v2-3-:
A = [1 5 7];
B = [1 2 3 6 9 10];
[Bprime,matches] = matching(A,B)
function [Bprime,matches] = matching(A,B)
C = (repmat(A',1,length(B)) - repmat(B,length(A),1)).^2;
[matches,~] = munkres(C);
Bprime = B(matches);
end
Assuming instead you want to find matches recursively, as suggested by your question, you could either walk through A, for each element in A find the closest remaining element in B and discard it (sortedmatching below); or you could iteratively form and discard the distance-minimizing match between remaining elements in A and B until all elements in A are matched (greedymatching):
A = [1 5 7];
B = [1 2 3 6 9 10];
[~,~,Bprime,matches] = sortedmatching(A,B,[],[])
[~,~,Bprime,matches] = greedymatching(A,B,[],[])
function [A,B,Bprime,matches] = sortedmatching(A,B,Bprime,matches)
[~,ix] = min((A(1) - B).^2);
matches = [matches ix];
Bprime = [Bprime B(ix)];
A = A(2:end);
B(ix) = Inf;
if(not(isempty(A)))
[A,B,Bprime,matches] = sortedmatching(A,B,Bprime,matches);
end
end
function [A,B,Bprime,matches] = greedymatching(A,B,Bprime,matches)
C = (repmat(A',1,length(B)) - repmat(B,length(A),1)).^2;
[minrows,ixrows] = min(C);
[~,ixcol] = min(minrows);
ixrow = ixrows(ixcol);
matches(ixrow) = ixcol;
Bprime(ixrow) = B(ixcol);
A(ixrow) = -Inf;
B(ixcol) = Inf;
if(max(A) > -Inf)
[A,B,Bprime,matches] = greedymatching(A,B,Bprime,matches);
end
end
While producing the same results in your example, all three methods potentially give different answers on the same data.
Normally I would run screaming from for and while loops in Matlab, but in this case I cannot see how the solution could be vectorized. At least it is O(N) (or near enough, depending on how many equally-close matches to each A(i) there are in B). It would be pretty simple to code the following in C and compile it into a mex file, to make it run at optimal speed, but here's a pure-Matlab solution:
function [out, ind] = greedy_nearest(A, B)
if nargin < 1, A = [1 5 7]; end
if nargin < 2, B = [1 2 3 6 9 10]; end
ind = A * 0;
walk = 1;
for i = 1:numel(A)
match = 0;
lastDelta = inf;
while walk < numel(B)
delta = abs(B(walk) - A(i));
if delta < lastDelta, match = walk; end
if delta > lastDelta, break, end
lastDelta = delta;
walk = walk + 1;
end
ind(i) = match;
walk = match + 1;
end
out = B(ind);
You could first get the absolute distance from each value in A to each value in B, sort them and then get the first unique value to a sequence when looking down in each column.
% Get distance from each value in A to each value in B
[~, minIdx] = sort(abs(bsxfun(#minus, A,B.')));
% Get first unique sequence looking down each column
idx = zeros(size(A));
for iCol = 1:numel(A)
for iRow = 1:iCol
if ~ismember(idx, minIdx(iRow,iCol))
idx(iCol) = minIdx(iRow,iCol);
break
end
end
end
The result when applying idx to B
>> idx
1 4 5
>> B(idx)
1 6 9

Modify struct array and return struct array

I have a struct array: a 1x10 struct array with fields: N, t, q, r, T, each of which is a vector of type double.
The 10 array entries each represent the outcome of a testing condition in an experiment. I would like to be able to make a function that takes two indices, index1 and index2, and modifies the constituent N, t, q, r vectors (T is a single number) so that they become length index1:index2. Something like
function sa = modifier(struct_array, index1, index2)
sa = structfun(#(x) x(index1:index2), struct_array, 'UniformOutput', false)
stuff
end
Now, where stuff is, I've tried using structfun and cellfun, see here except that those return a struct and a cell array, respectively, whereas I need to return a struct array.
The purpose of this is to be able to get certain sections of the experimental results, e.g. maybe the first five entries in each vector inside each cell correspond to the initial cycles of the experiment.
Please let me know if this is possible, and how I might go about it!
You can try this:
From this question's answer, I figured out how to loop through struct fields. I modified the code to address your question by extracting a subsample from each field that goes through the for loop and then copy the desired subset of that data into a new struct array with identically named fields.
% Define indexes for extraction
fieldsToTrim = {'a' 'b'};
idx = 2:3; % Create index vector for extracting selected data range
% Define test struct to be read
teststruct.a = [1 2 3];
teststruct.b = [4 5 6];
teststruct.c = [7 8 9];
% Get names of struct fields
fields = fieldnames(teststruct);
% Loop through each field and extract the subset
for i = 1:numel(fields)
if max(strcmp(fields{i},fieldsToTrim)) > 0
% If current matches one of the fields selected for extraction
% extract subset
teststructResults.(fields{i}) = teststruct.(fields{i})(idx);
else
% Else, copy all contents on field to resulting struct
teststructResults.(fields{i}) = teststruct.(fields{i});
end
end
Finally, to turn this into a function, you can modify the above code to this:
function teststructResults = extractSubsetFromStruct(teststruct,fieldsToTrim,idx1, idx2)
% idx1 and idx2 are the start and end indicies of the desired range
% fieldsToTrim is a string array of the field names you want
% included in the trimming, all other fields will be fully copied
% teststruct is your input structure which you are extracting the
% subset from
% teststructResults is the output containing identically named
% struct fields to the input, but only containing data from the selected range
idx = idx1:idx2; % Create index vector for extracting selected data range
% Get names of struct fields
fields = fieldnames(teststruct);
% Loop through each field and extract the subset
for i = 1:numel(fields)
if max(strcmp(fields{i},fieldsToTrim)) > 0
% If current matches one of the fields selected for extraction
% extract subset
temp = teststruct.(fields{i});
teststructResults.(fields{i}) = temp(idx);
else
% Else, copy all contents on field to resulting struct
teststructResults.(fields{i}) = teststruct.(fields{i});
end
end
end
I successfully ran the function like this:
teststruct =
a: [1 2 3]
b: [4 5 6]
c: [7 8 9]
>> extractSubsetFromStruct(teststruct,{'a' 'b'},2,3)
ans =
a: [2 3]
b: [5 6]
c: [7 8 9]

vectorize data from struct in matlab

I have created a struct by:
a(1).x = {[1.1 5 8], [3 5 6]};
a(2).x = {[3.1 0 4], [9 8 7]};
and wish to obtain an array with value [1.1 3.1].
I have tried:
a.x{1}(1,1)
Field reference for multiple structure elements that is
followed by more reference blocks is an error.
Any ideas please?
The syntax error tells that you cannot further sub-reference inside multiple struct elements. So, the obvious one-liner—much slower than a for loop—that saves memory would be:
arrayfun(#(y) y.x{1}(1), a)
Just for you to compare performance, the loop-based version
function A = my_extractor(S)
A = zeros(size(S));
N = numel(S);
for k = 1:N
A(k) = S(k).x{1}(1);
end;
end
If your .x field will always have the same dimensions then you could try
A = vertcat(a.x);
X = vertcat(A{:,1});
X(:,1)

Resources