How to vectorize operations on cells? - matlab

Given B = {"7" "8" "9"}, I'd like to use its values as respective replacements for the ultimate elements of each cell in A, i.e. transform
A = {{1 2} {3 4} {5 6}}
into
A = {{1 "7"} {3 "8"} {5 "9"}}
In unvectorized form, this can be written as:
nn = cellfun(#numel, A)
for i = 1:numel(a)
A{i}{nn(i)} = B{i};
end
All sub-cells may have the same length, but a solution for sub-cells of arbitrary length would be ideal.
The only solution I found involves making a reshaped copy of A:
nn = cellfun(#numel, A) #=> 2 2 2
A2 = horzcat(A{:}) #=> {1 2 3 4 5 6}
jj = cumsum(nn) #=> 2 4 6
[A2{jj}] = B{:} #=> {1 "7" 3 "8" 5 "9"}
ii = [1 jj(1:end-1)+1] #=> 1 3 5
A = cellslices(A2,ii,jj,2) #=> {{1 "7"} {3 "8"} {5 "9"}
I'd have hoped something like the below would work - but it doesn't:
octave:1> [[A{:}]{ii}] = B{:}
error: invalid lvalue function called in expression
Is there a way to vectorize this operation?

As learned from the comments that you actually just want to avoid an explicit for/while loop and are okay with cellfun (the use of which is not vectorisation), the process can be replicated with cellfun as follows:
A = cellfun(#(k) {A{k}{1:end-1} B{k}}, num2cell(1:numel(A)), 'un', 0);
or with arrayfun as:
A = arrayfun(#(k) {A{k}{1:end-1} B{k}}, 1:numel(A), 'un', 0);

Related

How to insert a column of characters to a matrix?

I'd like to insert a column of character to a matrix in MATLAB.
For example, we want to reach from first matrix to the second matrix:
first_matrix = [2 3; 4 5; 1 7]
second_matrix = [c 2 3; c 4 5; c 1 7]
In fact the reason is that, I have a output.txt file from a software. In that file, I should select a matrix in it, and change matrix column order. After doing this, i.e. reach to first_matrix, the output in form of second_matrix should be used in another software. So, finally I should save it in a text file format for second software.
You cannot do with this numeric arrays. The possible ways to do this are:
Using a categorical array i.e.
>> second_matrix = [num2cell(repmat('c',3,1)) categorical(first_matrix)]
ans =
3×3 categorical array
c 2 3
c 4 5
c 1 7
Using a character array i.e.
>> second_matrix = [repmat('c ',3,1) num2str(first_matrix)]
second_matrix =
3×7 char array
'c 2 3'
'c 4 5'
'c 1 7'
Using a string array (requires ≥ R2016b) i.e.
>> second_matrix = [repmat("c",3,1) first_matrix] %in ≥ R2017a
% second_matrix = [repmat(string('c'),3,1) first_matrix] %in ≥ R2016b
second_matrix =
3×3 string array
"c" "2" "3"
"c" "4" "5"
"c" "1" "7"
Using a cell array i.e.
>> second_matrix = [num2cell(repmat('c',3,1)) num2cell(first_matrix)]]
second_matrix =
3×3 cell array
{'c'} {[2]} {[3]}
{'c'} {[4]} {[5]}
{'c'} {[1]} {[7]}
Using a symbolic array (requires Symbolic Math Toolbox) i.e.
>> second_matrix = [repmat(sym('c'),3,1) first_matrix]
second_matrix =
[ c, 2, 3]
[ c, 4, 5]
[ c, 1, 7]

How to update the length of a vector in matlab if statement

I'm using MATLAB to write a function, which can find all elements in vector v that are equal to scalar a, and change each one of them to [b c], finally return the new vector w. If v = [1 2 5], a = 2, b = 4, c = 3, then the result is w = [1 4 3 5].
Below is my code
function w = change(v, a , b ,c )
for ii = 1:length(v)
if v(ii) == a
v_1 = v(1:ii-1);
v_2 = v(ii+1:length(v));
v_3 = [b c];
v = [v_1 v_3 v_2];
end
end
w = v;
end
However, the problem is: for statement will only read the length of the v before the first run of the loop, in this way, if the size of vector is increased, then length(v) in if statement will not be updated thus ii can not cover all the element indices. For example, if my input is ([1 2 2 2 3 2 4 5 6 2], 2, 4, 4), then with my code, for will stop at ii = 10, which is length of the old v, and give a wrong result [1 4 4 4 4 4 4 3 4 4 4 5 6 2], which does not change the last element (because ii doesn't cover 15).
My question is: how to update length(v) in if statement? or is there any other better way to finish this task?
Thank you very much.
I think this will be easier if you do it with cell arrays.
function w = change(v, a , b ,c )
cellV = num2cell(v);
cellW = cellfun(#(x)(subFunction(x,a,b,c)),cellV,'uni',0);
w = [cellW{:}];
end
function out = subFunction(val,a,b,c)
if (val == a)
out = [b c];
else
out = x;
end
end
A simpler method than checking each element in a loop is to find where the new elements will go, by using find(v==a) and just loop on those cases (taking care with the indexing!):
s = find(v==a);
v(end+1:end+numel(s))=0;
for ii=1:numel(s)
ind=s(ii)+ii;
v(ind-1:end)=[b c v(ind:end-1)];
end

Find row-wise combinations of a 2 dimensional matrix

I have a matrix:
X = [2,6,1; 3,8,1; 4,7,1; 6,2,1; 6,4,1; 7,3,1; 8,5,1; 7,6,1];
I want to find all row-wise combinations of X. i.e.
A(1) = [2, 6, 1; 3, 8, 1; 4, 7, 1]
A(2) = [2, 6, 1; 3, 8, 1; 6, 2, 1]
:
:
:
Here's what I've tried:
X = [2,6,1; 3,8,1; 4,7,1; 6,2,1; 6,4,1; 7,3,1; 8,5,1; 7,6,1];
p = 3
[m, n] = size(X);
comb = combnk(1:m, p);
[s, t] = size(comb);
c = [X(comb(:,1), :, :) X(comb(:,2), :, :) X(comb(:,3), :, :)];
This gives me a matrix like:
c = 2 6 1 3 8 1 4 7 1
2 6 1 3 8 1 6 2 1
2 6 1 3 8 1 6 4 1
I want to apply the concatenate matrix option to obtain c to make it dynamic depending on value of p but I'm not sure how to use it. I don't want to use For loops. Please help me out.
This is fully vectorized, so it should be fast:
n = 3; %// number of rows to pick each time
ind = reshape(nchoosek(1:size(X,1), n).', [], 1); %'// indices of combinations
A = permute(reshape(X(ind,:).', size(X,2), n, []), [2 1 3]);
The result is
A(:,:,1)
ans =
2 6 1
3 8 1
4 7 1
A(:,:,2)
ans =
2 6 1
3 8 1
6 2 1
etc.
Should you need the result in the form of a cell array, you can convert A from 3D-array to cell array this way:
A = mat2cell(A, size(A,1), size(A,2), ones(1,size(A,3)));
Your thinking is pretty close. This code does the job. I put comments in code, which should be easy to read.
X = [2,6,1; 3,8,1; 4,7,1; 6,2,1; 6,4,1; 7,3,1; 8,5,1; 7,6,1];
p = 3;
%// List all combinations choosing 3 out of 1:8.
v = nchoosek(1:size(X,1), p);
%// Use each row of v to create the matrices, and put the results in an cell array.
%// This is the A matrix in your question.
A = arrayfun(#(k)X(v(k,:), :), 1:size(v,1), 'UniformOutput', false);
%// And you can concatenate A vertically to get c.
flatA = cellfun(#(x)reshape(x, 1, []), A, 'UniformOutput', false);
c = vertcat(flatA{:});
PS: From my understanding I thought the result you wanted was A, which is an easy to use cell array. But I added an extra step to get c exactly as in your question just in case.
Disclaimer: arrayfun and cellfun are pretty much equivalent to for loop in terms of performance.
You can do it using reshape and a bunch of transposes since Matlab is column-major ordered:
c = reshape(X(comb',:)',9,[])'
or if you want a 3D matrix:
A = permute(reshape(X(comb',:)',3,3,[])', [2,1,3])

How to loop two vectors in MATLAB?

In python one can use zip to loop multiple vectors or enumerate to get the current index of the looped vector like so
one = ['A', 'B', 'C']
two = [1, 2, 3]
for i, j in zip(one, two):
print i, j
for j, i in enumerate(one):
print i, two[j]
Gives
>>>
A 1
B 2
C 3
A 1
B 2
C 3
In MATLAB it's possible to do
one = {'A' 'B' 'C'};
two = [1 2 3];
for i = 1:1:length(one)
printf('%s %i\n', one{i}, two(i));
endfor
j = 1;
for i = one
printf('%s %i\n', i{1}, two(j));
j = j + 1;
endfor
giving
A 1
B 2
C 3
A 1
B 2
C 3
So is one of those two options the common way how one would do it in MATLAB, i. e. to loop through several vectors "in parallel" or is there another, maybe better way?
Bonus:
two = [1 2 3];
two = [1, 2, 3];
Both of these lines give the same output in the upper MATLAB program. Whats the difference?
Using printf, or fprintf in Matlab, is pretty good. The Matlab code for your first approach is
one = {'A' 'B' 'C'};
two = [1 2 3];
for ii = 1:length(one)
fprintf('%s %i\n', one{ii}, two(ii));
end
It's also possible to put the strings into a cell array, without any for loop.
s = cellfun(#(a,b) [a,' ',b], one', ...
arrayfun(#num2str, two', 'UniformOutput', false),....
'UniformOutput', false)
Bonus:
>> A = [1;2;3]
A =
1
2
3
>> A = [1 2 3]
A =
1 2 3
>> A = [1,2,3]
A =
1 2 3
>> A = [1,2,3;4 5 6;7,8 9]
A =
1 2 3
4 5 6
7 8 9
>>
Bonus 2:
Using i and j is bad. See - Using i and j as variables in Matlab

How to assemble small n-d arrays into a larger n-d array?

The code below produces a cell array containing n (=210) 2x3x4-shaped n-d arrays.
n = prod(5:7);
makendarray = #(i) reshape(1:24, 2:4) + (i * 1000);
ndarrays = cellfun(makendarray, num2cell(1:n), 'un', 0);
For each i ∈ {1, … ,n}, the contents of the i-th n-d array are all integers whose initial digits match those of i. For example, for i = 123:
>> ndarrays{123}
ans(:,:,1) =
123001 123003 123005
123002 123004 123006
ans(:,:,2) =
123007 123009 123011
123008 123010 123012
ans(:,:,3) =
123013 123015 123017
123014 123016 123018
ans(:,:,4) =
123019 123021 123023
123020 123022 123024
I want to assemble all these n-d arrays into a larger [5x6x7x2x3x4 double] n-d array X in such a way that expressions of the form X(i,j,k,:,:,:) will correspond to one of the original smaller n-d arrays. This last requirement is the one that's giving me difficulty.
I have no problem producing the larger [5x6x7x2x3x4 double] n-d array from the smaller ones, but expressions of the form X(i,j,k,:,:,:) do not produce one of the original smaller n-d arrays.
Below is an example of one of the things I've tried; the last output should match below should match the output shown for ndarrays{123} above, but doesn't:
>> X = reshape(cell2mat(ndarrays), [5:7 2:4]);
>> [idx{1:3}] = ind2sub(5:7, 123);
>> squeeze(X(idx{:}, :, :, :))
ans(:,:,1) =
62001 62003 62005
167001 167003 167005
ans(:,:,2) =
62007 62009 62011
167007 167009 167011
ans(:,:,3) =
62013 62015 62017
167013 167015 167017
ans(:,:,4) =
62019 62021 62023
167019 167021 167023
EDIT: OK, by (pretty much blind) trial-and-error I found that the following does the trick:
tmp = cellfun(#(c) reshape(c, [1 24]), ndarrays, 'un', 0);
X = reshape(cat(1, tmp{:}), [5:7 2:4]);
IOW: linearize the subarrays before passing them to cat(1, ...). I'm surprised that it's necessary to explicitly perform this linearization step: it's what I expect MATLAB to do by default (in cell2mat, for example).
Be that as it may, is there a better (or at least clearer/easier to understand) way to achieve the same effect?
(BTW, for this problem, the initial shape of ndarray is given and not subject to change. IOW, solutions that require modifying the makesubarray function in the example do not fit the situation I'm dealing with.)
EDIT2: In reference to Luis Mendo's answer, here's the output (copy-pasted verbatim from my MATLAB workspace) of a small script (with echo on), showing the sizes of various items:
n = prod(5:7);
makendarray = #(i) reshape(1:24, 2:4) + (i * 1000);
ndarrays = cellfun(makendarray, num2cell(1:n), 'un', 0);
size(ndarrays)
ans =
1 210
size(permute(ndarrays, [2 3 4 1]))
ans =
210 1
size(cell2mat(permute(ndarrays, [2 3 4 1])))
ans =
420 3 4
echo off;
You can achieve it with a little bit of permute and reshape. The logic of this approach can be followed by observing size(X) at the end of each step (indicated in the comments):
X = cell2mat(permute(ndarrays(:), [2 3 4 1])); %// size [2 3 4 210]
X = permute(X, [4 5 6 1 2 3]); %// size [210 1 1 2 3 4]
X = reshape(X, [7 6 5 2 3 4]); %// size [7 6 5 2 3 4]
X = permute(X, [3 2 1 4 5 6]); %// size [5 6 7 2 3 4]