How to vectorize without knowing how to vectorize:
First, I'll only discuss vectorizing the first double loop, you can follow the same logic for the second loop.
I tried to show a thought process from scratch, so though the final answer is just 2 lines long, it is worthwhile to see how a beginner can try to obtain it.
First, I recommend "massaging" the code in simple cases, to get a feel for it. For example, I've used n=3
and v=1:6
and run the first loop once, this is how B
looks like:
[N M]=size(B)
N =
9
M =
27
imagesc(B);
So you can see we get a staircase like matrix, which is pretty regular! All we need is to assign the right matrix index to the right value of v
and that's it.
There are many ways to achieve that, some more elegant than others. One of the simplest ways is to use the function find
:
pos=[find(B==v(1)),find(B==v(2)),find(B==v(3)),...
find(B==v(4)),find(B==v(5)),find(B==v(6))]
pos =
1 10 19 28 37 46
29 38 47 56 65 74
57 66 75 84 93 102
85 94 103 112 121 130
113 122 131 140 149 158
141 150 159 168 177 186
169 178 187 196 205 214
197 206 215 224 233 242
The values above are the linear indices of matrix B
where values of v
were found. Each column represents the linear index of a specific value of v
in B
. For example, indices [1 29 57 ...]
all contain the value v(1)
, etc... Each row contains v
entirely, so, indices [29 38 47 56 65 74] contain v=[v(1) v(2) ... v(6)]
. You can notice that for each row the difference between indices is 9, or, each index is separated with a step size N
, and there are 6 of them, which is just the length of vector v
(also obtained by numel(v)
). For the columns, the difference between adjacent elements is 28, or, the step size is M+1
.
We shall simply need to assign the values of v
in the proper indices according to this logic. one way is to write each "row":
B([1:N:numel(v)*N]+(M+1)*0)=v;
B([1:N:numel(v)*N]+(M+1)*1)=v;
...
B([1:N:numel(v)*N]+(M+1)*(N-2))=v;
But that is impractical for big N-2
, so you can do it in a for loop if you really want:
for kk=0:N-2;
B([1:N:numel(v)*N]+(M+1)*kk)=v;
end
Matlab offers a more efficient way to get all the indices at once using bsxfun
(this replaces the for loop), for example:
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]')
so now we can use ind
to assign v
into the matrix N-1
times. For that we need to "flatten" ind
into a row vector:
ind=reshape(ind.',1,[]);
and concatenate v
to itself N-1 times (or make N-1 more copies of v
):
vec=repmat(v,[1 N-1]);
finally we get the answer:
B(ind)=vec;
Making a long story short, and writing it all compactly, we get a 2 line solution (given size B
is already known: [N M]=size(B)
) :
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]');
B(reshape(ind.',1,[]))=repmat(v,[1 N-1]);
For n=9
the vectorized code is ~850 faster in my machine. (small n
will be less significant)
Since the matrix obtained is mostly made up from zeros, you don't need to assign these to a full matrix, instead use a sparse matrix, Here's the full code for that (pretty similar):
N=3^(n-1);
M=3^n;
S=sparse([],[],[],N,M);
ind=bsxfun(@plus,1:N:N*numel(v),[0:(M+1):M*(N-1)+1]');
S(reshape(ind.',1,[]))=repmat(v,[1 N-1]);
For n=10
I could only run the sparse matrix code (out of memory otherwise), and in my machine it took ~6 seconds.
Now try to apply this to the second loop...
j
as a variable name in Matlab. – Clime