Indexing a vector within a bigger one in MATLAB - matlab

I'm trying to find the index position of the smaller vector inside a bigger one.
I've already solved this problem using strfind and bind2dec,
but I don't want to use strfind, I don't want to convert to string or to deciamls at all.
Given the longer vector
a=[1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,1,1];
I want to find the index of the smaller vector b inside a
b=[1,1,1,0,0,0];
I would expect to find as result:
result=[15,16,17,18,19,20];
Thank you

Here is as solution using 1D convolution. It may find multiple matches so start holds beginning indices of sub-vectors:
f = flip(b);
idx = conv(a,f,'same')==sum(b) & conv(~a,~f,'same')==sum(~b);
start = find(idx)-ceil(length(b)/2)+1;
result = start(1):start(1)+length(b)-1;

Solution with for loops:
a=[1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,1,1];
b=[1,1,1,0,0,0];
c = [];
b_len = length(b)
maxind0 = length(a) - b_len + 1 %no need to search higher indexes
for i=1:maxind0
found = 0;
for j=1:b_len
if a(i+j-1) == b(j)
found = found + 1;
else
break;
end
end
if found == b_len % if sequence is found fill c with indexes
for j=1:b_len
c(j)= i+j-1;
end
break
end
end
c %display c

Does it need to be computationally efficient?
A not very efficient but short solution would be this:
a=[1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,1,1];;
b=[1,1,1,0,0,0];
where = find(arrayfun(#(n) all(a(n+1:n+length(b))==b),0:length(a)-length(b)));
... gives you 15. Your result would be the vector where:where+length(b)-1.
edit: I tried it and I stand corrected. Here is a version with loops:
function where = find_sequence(a,b)
na = 0;
where = [];
while na < length(a)-length(b)
c = false;
for nb = 1:length(b)
if a(na+nb)~=b(nb)
na = na + 1; % + nb
c = true;
break
end
end
if ~c
where = [where,na+1];
na = na + 1;
end
end
Despite its loops and their bad reputation in Matlab, it's a lot faster:
a = round(rand(1e6,1));
b = round(rand(10,1));
tic;where1 = find(arrayfun(#(n) all(a(n+1:n+length(b))==b),0:length(a)-length(b)));toc;
tic;where2 = find_sequence(a,b);toc;
>> test_find_sequence
Elapsed time is 4.419223 seconds.
Elapsed time is 0.042969 seconds.

A neater method using for would look like this, there is no need for an inner checking loop as we can vectorize that with all...
a = [1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,0,1,1];
b = [1,1,1,0,0,0];
idx = NaN( size(b) ); % Output NaNs if not found
nb = numel( b ); % Store this for re-use
for ii = 1:numel(a)-nb+1
if all( a(ii:ii+nb-1) == b )
% If matched, update the index and exit the loop
idx = ii:ii+nb-1;
break
end
end
Output:
idx = [15,16,17,18,19,20]
Note, I find this a bit easier to read that some of the nested solutions, but it's not necessarily faster, since the comparison is done on all elements in b each time.

Related

Nested for loop not outputing values for inner loop

I have a problem with this nested for loop:
eta = [1e-3:1e-2:9e-1];
HN =5;
for ii = 1:numel(eta)
for v = 1:HN
DeltaEta(v) = eta(ii)*6;
end
end
This code gives the output of DeltaEta as a 1x5 vector.
However, I want the result to be 90x5 vector where DeltaEta is computed 5 times for each value of eta.
I believe the problem is with the way I am nesting the loops.
It seems trivial but I can't get the desired output, any leads would be appreciated.
You're assigning outputs to DeltaEta(v), where v = 1,2,..,HN. So you're only ever assigning to
DeltaEta(1), DeltaEta(2), ..., DeltaEta(5)
You can solve this with a 2D matrix output, indexing on ii too...
eta = [1e-3:1e-2:9e-1];
HN = 5;
DeltaEta = NaN( numel(eta), HN );
for ii = 1:numel(eta)
for v = 1:HN
DeltaEta(ii,v) = eta(ii)*6;
end
end
% optional reshape at end to get column vector
DeltaEta = DeltaEta(:);
Note, there is no change within your inner loop - DeltaEta is the same for all values of v. That means you can get rid of the inner loop
eta = [1e-3:1e-2:9e-1];
HN = 5;
DeltaEta = NaN( numel(eta), HN );
for ii = 1:numel(eta)
DeltaEta( ii, : ) = eta(ii) * 6;
end
And now we can see a way to actually remove the outer loop too
eta = [1e-3:1e-2:9e-1];
HN = 5;
DeltaEta = repmat( eta*6, HN, 1 ).';
To answer your question as asked, you need to index on ii as well as v:
eta = [1e-3:1e-2:9e-1];
HN =5;
for ii = 1:numel(eta)
for v = 1:HN
DeltaEta(ii,v) = eta(ii)*6;
end
end
However this is in general a bad idea -- if you catch yourself using for-loops in MATLAB (particularly doubly-nested for-loops) you should consider if there might be a better way that uses MATLAB's strong vectorisation abilities.

Replicate vectors shifting them to the right

In Matlab, I have two single row (1x249) vectors in a 2x249 matrix and I have to create a matrix A by replicating them many times, each time shifting the vectors of 2 positions to the right. I would like to fill the entries on the left with zeros. Is there a smart way to do this? Currently, I am using a for loop and circshift, and I add at each iteration I add the new row to A, but probably this is highly inefficient.
Code (myMat is the matrix I want to shift):
A = [];
myMat = [1 0 -1 zeros(1,246); 0 2 0 -2 zeros(1,245)];
N = 20;
for i=1:N-1
aux = circshift(myMat,[0,2*(i-1)]);
aux(:,1:2*(i-1)) = 0;
A =[A; aux];
end
As you are probably aware, loops in Matlab are not so efficient.
I know that the Mathworks keep saying this is no longer so with JIT
compilation, but I haven't experienced the fast loops yet.
I put your method for constructiong the matrix A in a function:
function A = replvector1(myMat,shift_right,width,N)
pre_alloc = true; % make implementation faster using pre-allocation yes/no
% Pad myMat with zeros to make it wide enough
myMat(1,width)=0;
% initialize A
if pre_alloc
A = zeros(size(myMat,1)*(N-1),width);
else
A = [];
end
% Fill A
for i=1:N-1
aux = circshift(myMat,[0,shift_right*(i-1)]);
aux(:,1:min(width,shift_right*(i-1))) = 0;
A(size(myMat,1)*(i-1)+1:size(myMat,1)*i,:) =aux;
end
Your matrix-operation looks a lot like a kronecker product, but the
block-matrixces have overlapping column ranges so a direct kronecker product
will not work. Instead, I constructed the following function:
function A = replvector2(myMat,shift_right,width,N)
[i,j,a] = find(myMat);
i = kron(ones(N-1,1),i) + kron([0:N-2]',ones(size(i))) * size(myMat,1);
j = kron(ones(N-1,1),j) + kron([0:N-2]',ones(size(j))) * shift_right;
a = kron(ones(N-1,1),a);
ok = j<=width;
A = full(sparse(i(ok),j(ok),a(ok),(N-1)*size(myMat,1),width));
You can follow the algorithm by removing semicolons and looking at intermediate
results.
The following main program runs your example, and can easily be modified to
run similar examples:
% inputs (you may vary them to see that it always works)
shift_right = 2;
width = 249;
myMat1 = [ 1 0 -1 0 ;
0 2 0 -2 ];
N = 20;
% Run your implementation
tic;
A = replvector1(myMat,shift_right,width,N);
disp(sprintf('\n original implementation took %e sec',toc))
% Run the new implementation
tic;
B = replvector2(myMat,shift_right,width,N);
disp(sprintf(' new implementation took %e sec',toc))
disp(sprintf('\n norm(B-A)=%e\n',norm(B-A)))
I've taken Nathan's code (see his answer to this question), and added another possible implementation (replvector3).
My idea here stems from you not really needing a circular shift. You need to right-shift and add zeros to the left. If you start with a pre-allocated array (this is really where the big wins in time are for you, the rest is peanuts), then you already have the zeros. Now you just need to copy over myMat to the right locations.
These are the times I see (MATLAB R2017a):
OP's, with pre-allocation: 1.1730e-04
Nathan's: 5.1992e-05
Mine: 3.5426e-05
^ shift by one on purpose, to make comparison of times easier
This is the full copy, copy-paste into an M-file and run:
function so
shift_right = 2;
width = 249;
myMat = [ 1 0 -1 0 ;
0 2 0 -2 ];
N = 20;
A = replvector1(myMat,shift_right,width,N);
B = replvector2(myMat,shift_right,width,N);
norm(B(:)-A(:))
C = replvector3(myMat,shift_right,width,N);
norm(C(:)-A(:))
timeit(#()replvector1(myMat,shift_right,width,N))
timeit(#()replvector2(myMat,shift_right,width,N))
timeit(#()replvector3(myMat,shift_right,width,N))
% Original version, modified to pre-allocate
function A = replvector1(myMat,shift_right,width,N)
% Assuming width > shift_right * (N-1) + size(myMat,2)
myMat(1,width) = 0;
M = size(myMat,1);
A = zeros(M*(N-1),width);
for i = 1:N-1
aux = circshift(myMat,[0,shift_right*(i-1)]);
aux(:,1:shift_right*(i-1)) = 0;
A(M*(i-1)+(1:M),:) = aux;
end
% Nathan's version
function A = replvector2(myMat,shift_right,width,N)
[i,j,a] = find(myMat);
i = kron(ones(N-1,1),i) + kron((0:N-2)',ones(size(i))) * size(myMat,1);
j = kron(ones(N-1,1),j) + kron((0:N-2)',ones(size(j))) * shift_right;
a = kron(ones(N-1,1),a);
ok = j<=width;
A = full(sparse(i(ok),j(ok),a(ok),(N-1)*size(myMat,1),width));
% My trivial version with loops
function A = replvector3(myMat,shift_right,width,N)
% Assuming width > shift_right * (N-1) + size(myMat,2)
[M,K] = size(myMat);
A = zeros(M*(N-1),width);
for i = 1:N-1
A(M*(i-1)+(1:M),shift_right*(i-1)+(1:K)) = myMat;
end

Avoid for-loop to increase performance

Is it possible to obtain the matrix A in a more efficient way than using a for loop?
a = 6; % constant
b = 2; % constant
s = 0.1; % possible to change
I = 12; % possible to change
A = zeros(a,I+1);
A(:,1) = rand(a,1); % some initial value
B = rand(b,I);
% possible to avoid for-loop to increase performance?
for i = 1:I
A(:,i+1) = fun(A(:,i),B(:,i), a, s);
end
The function fun is given as
function [AOut] = fun(AIn, B, a, s)
AOut = zeros(a,1);
AOut(1) = AIn(1) + AIn(4)*s*cos(AIn(3));
AOut(2) = AIn(2) + AIn(4)*s*sin(AIn(3));
AOut(3) = AIn(3) + AIn(4)*AIn(6)*s;
AOut(4) = AIn(4) + AIn(5)*s;
AOut(5) = AIn(5) + B(1);
AOut(6) = AIn(6) + B(2);
end
i dont think you can optimize the loop in regards to effiency as the last values are needed for calculating the next. #Tony Tannous showed you a nice way how to get rid of the loop in your code. For better performance at high values of I you can change fun() to:
function [AOut] = fun2(AIn, B, a, s)
AOut=AIn+ [AIn(4)*s*cos(AIn(3)) ;...
AIn(4)*s*sin(AIn(3)) ;...
AIn(4)*AIn(6)*s ;...
AIn(5)*s ; ...
B(1) ; ...
B(2)];
end
First of all, if a != n
You will get an error:
Subscripted assignment dimension mismatch.
So you must be cautious
And, to get rid of the loop you can do this:
EDIT:
Apparently i isn't getting modified on the right side. I'll try to fix it and reedit my answer.
anyway, to get rid of the loop you can still use A(:, i:1:I)
i = 1;
A(:, i:1:I) = fun(A(:,i),B(:,i), a, s);
If you have any further question, please ask!

Storing non-zero integers from one matrix into another

I'm attempting to create a loop that reads through a matrix (A) and stores the non-zero values into a new matrix (w). I'm not sure what is wrong with my code.
function [d,w] = matrix_check(A)
[nrow ncol] = size(A);
total = 0;
for i = 1:nrow
for j = 1:ncol
if A(i,j) ~= 0
total = total + 1;
end
end
end
d = total;
w = [];
for i = 1:nrow
for j = 1:ncol
if A(i,j) ~= 0
w = [A(i,j);w];
end
end
end
The second loop is not working (at at least it is not printing out the results of w).
You can use nonzeros and nnz:
w = flipud(nonzeros(A)); %// flipud to achieve the same order as in your code
d = nnz(A);
The second loop is working. I'm guessing you're doing:
>> matrix_check(A)
And not:
>> [d, w] = matrix_check(A)
MATLAB will only return the first output unless otherwise specified.
As an aside, you can accomplish your task utilizing MATLAB's logical indexing and take advantage of the (much faster, usually) array operations rather than loops.
d = sum(sum(A ~= 0));
w = A(A ~= 0);

is it possible remove for loops from my code?

i want to remove nested for loops from my code?
i can't remove them.
k = 3;
Data = rand(100,5);
m = zeros(size(Data));
N = size(Data,2); % number of features
M = size(Data,1); % number of objects
bound = zeros(N,k+1);
MAX = max(Data);
MIN = min(Data);
for ii = 1:N
bound(ii,:) = linspace(MIN(ii), MAX(ii), k+1);
end
bound(:,end) = bound(:,end)+eps;
tic;
for ii = 1:M
for jj=1:N
for kk=1:k
if bound(jj,kk)<=Data(ii,jj) && Data(ii,jj)<bound(jj,kk+1)
m(ii,jj) = kk;
end
end
end
end
You can do away with nesting upto a certain limit.
At a glance, as the jj index seems to be uniform in the operation within the nested loop, you can replace
for ii = 1:M
for jj=1:N
for kk=1:k
if bound(jj,kk)<=Data(ii,jj) && Data(ii,jj)<bound(jj,kk+1)
m(ii,jj) = kk;
end
end
end
end
by simply
for ii = 1:M
for kk=1:k
m(ii,(bound(:,kk)<=Data(ii,:)' & Data(ii,:)'<bound(:,kk+1))) = kk;
end
end
This would give you the exact same result as before.
Since your longest loop is over ii=1:M, we should prioritise vectorising this one over the others. The smallest loop is over kk=1:k so this one can probably stay without worrying about it too much. You can use bsxfun to great effect in vectorisations of this sort:
for kk = 1:k
ind = bsxfun(#le, bound(:, kk)', Data) & bsxfun(#gt, bound(:, kk+1)', Data);
m(ind) = kk;
end
This gives the same result as your above code.
Another alternative is histc(), which is specifically designed for binning:
for jj = 1:N
[~, m(:,jj)] = histc(Data(:,jj),bound(jj,:));
end
This solution is on par with bsxfun() but it's not a very meaningful comparison because here the loop is across columns while with bsxfun is across bounds. Therefore, as a rule of thumb I would go with histc() if I have less columns than bounds, otherwise bsxfun().