Matlab: Convert elements larger (smaller) than 1 (-1) into a sequence of 1 (-1)

UPDATE: I did some testing, and the Jonas solution is the fastest for a number of input vectors of various sizes. In particular, as the hangar emphasizes, the solution scales to large sizes incredibly well - an important test, since these are usually large problems that make us ask such questions on SO. Thanks to both Jonas and tmpearce for your solutions - based on the efficiency of solving large size problems, I give the answer to the Jonas tick.

My question is: I have this column vector:

Vec = [0; 1; 2; -1; -3; 0; 0; 2; 1; -1]; 

I would like to convert each element, more than one, into a sequence of units whose length is equal to the value of the element. Similarly, I want to convert each element less than minus one into a sequence of minus. So my output vector should look like this:

 VecLong = [0; 1; 1; 1; -1; -1; -1; -1; 0; 0; 1; 1; 1; -1]; 

Note that each 2 has been changed to two 1, and -3 has been changed to three -1. I am currently solving the problem as follows:

 VecTemp = Vec; VecTemp(VecTemp == 0) = 1; VecLong = NaN(sum(abs(VecTemp)), 1); c = 1; for n = 1:length(Vec) if abs(Vec(n)) <= 1 VecLong(c) = Vec(n); c = c + 1; else VecLong(c:c + abs(Vec(n))) = sign(Vec(n)); c = c + abs(Vec(n)); end end 

It is not very elegant. Can anyone suggest a better method? Note. It can be assumed that Vec will contain only integer values. Thanks in advance for any suggestions.

+4
source share
2 answers

You can use the good old cumsum approach to repeat entries correctly. Please note that I assign several temporary variables that you can get rid of if you want to put everything on one line.

 %# create a list of values to repeat signVec = sign(Vec); %# create a list of corresponding indices that repeat %# as often as the value in signVec has to be repeated tmp = max(abs(Vec),1); %# max: zeros have to be repeated once index = zeros(sum(tmp),1); index([1;cumsum(tmp(1:end-1))+1])=1; %# assign ones a pivots for cumsum index = cumsum(index); %# create repeating indices %# repeat out = signVec(index); out' out = 0 1 1 1 -1 -1 -1 -1 0 0 1 1 1 -1 
+3
source

Edit: I thought of a different (slightly obscure), but shorter way to do this, and it is faster than the loop.

 for rep=1:100000 #% original loop-based solution end toc Elapsed time is 2.768822 seconds. #% bsxfun-based indexing alternative tic; for rep=1:100000 TempVec=abs(Vec);TempVec(Vec==0)=1; LongVec = sign(Vec(sum(bsxfun(@gt,1:sum(TempVec),cumsum(TempVec)))+1)) end toc Elapsed time is 1.798339 seconds. 

This answer is also very well evaluated compared to the original - at least to a certain extent. There is a good place.

 Vec = repmat(OrigVec,10,1); #% test with 100,000 loops #% loop-based solution: Elapsed time is 19.005226 seconds. #% bsxfun-based solution: Elapsed time is 4.411316 seconds. Vec = repmat(OrigVer,1000,1); #% test with 1,000 loops - 100,000 would be horribly slow #% loop-based solution: Elapsed time is 18.105728 seconds. #% bsxfun-based solution: Elapsed time is 98.699396 seconds. 

bsxfun decomposes the vector into a matrix, and then collapses it with the sum. With very large vectors, this is unnecessarily heavy in memory compared to a loop, so it ends up losing. Until then, however, all is well.


Original, slow answer:

Here is a single line:

 out=cell2mat(arrayfun(@(x) repmat(((x>0)*2)-1+(x==0),max(1,abs(x)),1),Vec,'uni',0)); out' = 0 1 1 1 -1 -1 -1 -1 0 0 1 1 1 -1 

What's happening:

 ((x>0)*2)-1 + (x==0) #% if an integer is >0, make it a 1, <0 becomes -1, 0 stays 0 max(1,abs(x)) #% figure out how many times to replicate the value arrayfun(@(x) (the above stuff), Vec, 'uni', 0) #% apply the function #% to each element in the array, generating a cell array output cell2mat( (the above stuff) ) #% convert back to a matrix 
+3
source

Source: https://habr.com/ru/post/1442437/


All Articles