For each element in vector, sum previous n elements - matlab

I am trying to write a function that sums the previous n elements for each the element
v = [1 1 1 1 1 1];
res = sumLastN(v,3);
res = [0 0 3 3 3 3];
Until now, I have written the following function
function [res] = sumLastN(vec,ppts)
if iscolumn(vec)~=1
error('First argument must be a column vector')
end
sz_x = size(vec,1);
res = zeros(sz_x,1);
if sz_x > ppts
for jj = 1:ppts
res(ppts:end,1) = res(ppts:end,1) + ...
vec(jj:end-ppts+jj,1);
end
% for jj = ppts:sz_x
% res(jj,1) = sum(vec(jj-ppts+1:jj,1));
% end
end
end
There are around 2000 vectors of about 1 million elements, so I was wondering if anyone could give me any advice of how I could speed up the function.

Using cumsum should be much faster:
function [res] = sumLastN(vec,ppts)
w=cumsum(vec)
res=[zeros(1,ppts-1),w(ppts+1:end)-w(1:end-ppts)]
end

You basically want a moving average filter, just without the averaging.
Use a digital filter:
n = 3;
v = [1 1 1 1 1 1];
res = filter(ones(1,n),1,v)
res =
1 2 3 3 3 3
I don't get why the first two elements should be zero, but why not:
res(1:n-1) = 0
res =
0 0 3 3 3 3

Related

Shuffle vector elements such that two similar elements coming together at most twice

For the sake of an example:
I have a vector named vec containing ten 1s and ten 2s. I am trying to randomly arrange it but with one condition that two same values must not come together more than twice.
What I have done till now is generating random indexes of vec using the randperm function and shuffling vec accordingly. This is what I have:
vec = [1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2];
atmost = 2;
indexes = randperm(length(vec));
vec = vec(indexes);
>> vec =
2 1 1 1 2 1 2 1 2 1 1 1 2 2 1 2 1 2 2 2
My code randomly arranges elements of vec but does not fulfill the condition of two similar values coming at most two times. How can I do this? Any ideas?
You can first determine the lengths of the runs of one value, and then fit the other values around them.
In the explanation I'll use values of a and b in the vector, so as not to confuse the values of the elements of the vector (1 and 2) and the lengths of the runs for each element (1 and 2).
The end goal is to use repelem to construct the shuffled vector. repelem takes a vector of the elements to repeat, and a vector of how many times to repeat each element. For example, if we have:
v = [b a b a b a b a b a b a b a b a b]
n = [1 1 1 2 1 1 2 1 2 1 1 1 1 2 1 1 0]
repelem will return:
shuffled_vec = [b a b a a b a b b a b b a b a b a a b a]
As a first step, I'll generate random values for the counts corresponding to the a values. In this example, that would be:
a_grouping = [1 2 1 1 1 1 2 1]
First, randomly select the number of 2's in the grouping vector. There can be at most n/2 of them. Then add 1's to make up the desired total.
num_total = 10; % number of a's (and b's)
% There can be 0..num_total/2 groups of two a's in the string.
two_count = randi(num_total/2 + 1) - 1;
% The remainder of the groups of a's have length one.
one_count = num_total - (2*two_count);
% Generate random permutation of two_count 2's and one_count 1's
a_grouping = [repmat(2, 1, two_count), repmat(1, 1, one_count)];
This will give us something like:
a_grouping = [2 2 1 1 1 1 1 1]
Now shuffle:
a_grouping = a_grouping(randperm(numel(a_grouping)));
With the result:
a_grouping = [1 2 1 1 1 1 2 1]
Now we need to figure out where the b values go. There must be at least one b between each run of a values (and at most two), and there may be 0, 1 or 2 b values at the beginning and end of the string. So we need to generate counts for each of the x and y values below:
all_grouping = [y 1 x 2 x 1 x 1 x 1 x 1 x 2 x 1 y]
The x values must be at least 1, so we'll assign them first. Since the y values can be either 0, 1 or 2, we'll leave them set to 0.
% Between each grouping of a's, there must be at least one b.
% There can be 0, 1, or 2 b's before and after the a's,
% so make room for them as well.
b_grouping = zeros(1, numel(a_grouping) - 1 + 2);
b_grouping(2:end-1) = 1; % insert one b between each a group
For each of the the remaining counts we need to assign, just select a random slot. If it's not filled yet (i.e. if it's < 2), increment the count, otherwise find a different slot.
% Assign location of remaining 2's
for s = numel(a_grouping):num_total
unassigned = true;
while unassigned
% generate random indices until we find one that's open
idx = randi(numel(b_grouping));
if b_grouping(idx) < 2
b_grouping(idx) = b_grouping(idx) + 1;
unassigned = false;
end
end
end
Now we've got separate counts for the a's and b's:
a_grouping = [1 2 1 1 1 1 2 1]
b_grouping = [1 1 1 2 2 1 1 1 0]
We'll build the value vector (v from the start of the example) and interleave the groupings (the n vector).
% Interleave the a and b values
group_values = zeros(1, numel(a_grouping) + numel(b_grouping));
group_values(1:2:end) = 2;
group_values(2:2:end) = 1;
% Interleave the corresponding groupings
all_grouping = zeros(size(group_values));
all_grouping(2:2:end) = a_grouping;
all_grouping(1:2:end) = b_grouping;
Finally, repelem puts everything together:
shuffled_vec = repelem(group_values, all_grouping)
The final result is:
shuffled_vec =
1 2 2 1 1 2 1 1 2 2 1 1 2 2 1 1 2 2 1 2
Full code:
num_total = 10; % number of a's (and b's)
% There can be 0..num_total/2 groups of two a's in the string.
two_count = randi(num_total/2 + 1) - 1;
% The remainder of the groups of a's have length one.
one_count = num_total - (2*two_count);
% Generate random permutation of two_count 2's and one_count 1's
a_grouping = [repmat(2, 1, two_count), repmat(1, 1, one_count)];
a_grouping = a_grouping(randperm(numel(a_grouping)));
% disp(a_grouping)
% Between each grouping of a's, there must be at least one b.
% There can be 0, 1, or 2 b's before and after the a's,
% so make room for them as well.
b_grouping = zeros(1, numel(a_grouping) - 1 + 2);
b_grouping(2:end-1) = 1; % insert one b between each a group
% Assign location of remaining 2's
for s = numel(a_grouping):num_total
unassigned = true;
while unassigned
% generate random indices until we find one that's open
idx = randi(numel(b_grouping));
if b_grouping(idx) < 2
b_grouping(idx) = b_grouping(idx) + 1;
unassigned = false;
end
end
end
% Interleave the a and b values
group_values = zeros(1, numel(a_grouping) + numel(b_grouping));
group_values(1:2:end) = 2;
group_values(2:2:end) = 1;
% Interleave the corresponding groupings
all_grouping = zeros(size(group_values));
all_grouping(2:2:end) = a_grouping;
all_grouping(1:2:end) = b_grouping;
shuffled_vec = repelem(group_values, all_grouping)
This should generate fairly random non-consecutive vectors. Whether it covers all possibilities uniformly, I'm not sure.
out=[];
for i=1:10
if randi(2)==1
out=[out,1,2];
else
out=[out,2,1];
end
end
disp(out)
Example results
1,2,1,2,1,2,1,2,2,1,2,1,1,2,2,1,2,1,1,2,
2,1,2,1,1,2,2,1,2,1,1,2,2,1,2,1,1,2,1,2,
1,2,2,1,2,1,1,2,1,2,2,1,2,1,2,1,2,1,2,1,
2,1,2,1,2,1,1,2,1,2,2,1,1,2,2,1,2,1,1,2,
2,1,1,2,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,
2,1,1,2,2,1,2,1,1,2,1,2,2,1,1,2,1,2,2,1,
1,2,2,1,1,2,1,2,2,1,2,1,2,1,1,2,2,1,1,2,
2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,2,1,2,1,2,
1,2,2,1,1,2,1,2,2,1,2,1,1,2,1,2,1,2,1,2,
1,2,1,2,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,

Create vector which ascends based on repeated values of another vector (MATLAB)

Here's a relatively simply one. Let's say I have the following vector ('V1'):
1
1
1
2
2
2
2
3
3
4
4
4
4
5
5
5
I want to create a second vector, V2, which starts at 1 and increases for each iteration of a value in V1, but then resets at a new value of V1. So for instance:
1
2
3
1
2
3
4
1
2
1
2
3
4
1
2
3
There may be a single iteration of a value in V1, or as many as 6.
The solution might use a for loop, but I imagine there is a simpler form without needing a loop ('repmat' comes to mind).
Another suggestion without a loop.
First count the number of repeated values
a=histc(v1,unique(v1));
Construct counting array
b = ones(1,sum(a));
Now counter the cumulated sum the appropriate places:
a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;
finally take the cumulated sum
cumsum(b)
In total
v1 = [1,1,1,1,2,2,3,3,3,3,3,4];
a=histcounts(v1,[unique(v1),inf]);
b = ones(1,sum(a));
a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;
disp(cumsum(b))
TIMEITs:
Running on random sorted input V1 = sort(randi(100,1e6,1)); I obtain the following timings in Matlab 2017a.
Gnovic's first suggestion: 2.852872e-02
Gnovic's second suggestion: 2.909344e-02
AVK's suggestion: 3.935982e-01
RadioJava's suggestion: 2.441206e-02
Nicky's suggestion: 9.153147e-03
Code for reference:
function [] = SO()
V1 = sort(randi(100,1e6,1));
t1 = timeit(#() gnovice1(V1)); fprintf("* Gnovic's first suggestion: %d\n",t1);
t2 = timeit(#() gnovice2(V1)); fprintf("* Gnovic's second suggestion: %d\n",t2);
t3 = timeit(#() AVK(V1)); fprintf("* AVK's suggestion: %d\n",t3);
t4 = timeit(#() RadioJava(V1)); fprintf("* RadioJava's suggestion: %d\n",t4);
t5 = timeit(#() Nicky(V1)); fprintf("* Nicky's suggestion: %d\n",t5);
function []=gnovice1(V1)
V2 = accumarray(V1, 1, [], #(x) {1:numel(x)});
V2 = [V2{:}].';
function []=gnovice2(V1)
V2 = ones(size(V1));
V2([find(diff(V1))+1; end]) = 1-accumarray(V1, 1);
V2 = cumsum(V2(1:end-1));
function []=AVK(v)
a= v;
for i=unique(v)'
a(v==i)= 1:length(a(v==i));
end
function []=RadioJava(vec)
vec = vec(:).';
zero_separated=[1,vec(1:end-1)==vec(2:end)];
c=cumsum(zero_separated);
zeros_ind = ~zero_separated;
d = diff([1 c(zeros_ind)]);
zero_separated(zeros_ind) = -d;
output=cumsum(zero_separated);
function []=Nicky(v1)
v1 = v1(:).';
a=histcounts(v1,[unique(v1),inf]);
b = ones(1,sum(a));
a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;
b = cumsum(b);
Assuming V1 is sorted, here's a vectorized solution using accumarray:
V2 = accumarray(V1, 1, [], #(x) {1:numel(x)});
V2 = [V2{:}].';
Based on the second approach in this answer:
t = diff([0; find([diff(V1)~=0; true])]);
V2 = ones(sum(t), 1);
V2(cumsum(t(1:end-1))+1) = 1-t(1:end-1);
V2 = cumsum(V2);
Solution without a loop
vec =[1 1 1 2 2 2 3 3 3];
zero_separated=[1,vec(1:end-1)==vec(2:end)]; % 0 at every new set
c=cumsum(zero_separated); % Temporary cumsum
zeros_ind = ~zero_separated;
d = diff([1 c(zeros_ind)]); % deltas in the temporary cumsum
zero_separated(zeros_ind) = -d; % Set zeros ind to delta
output=cumsum(zero_separated); % Calculate cumsum now
Output
output = 1 2 3 1 2 3 1 2 3
Based on this
v = [1;1;1;2;2;2;2;3;3;4;4;4;4;5;5;5];
a= v;
for i=unique(v)'
a(v==i)= 1:length(a(v==i));
end
disp(a)

Constructing vectors of different lengths

I want to find out row and column number of zeros in 3 dimensional space. Problem is I get output vectors(e.g row) of different length each time, hence dimension error occurs.
My attempt:
a (:,:,1)= [1 2 0; 2 0 1; 0 0 2]
a (:,:,2) = [0 2 8; 2 1 0; 0 0 0]
for i = 1 : 2
[row(:,i) colum(:,i)] = find(a(:,:,i)==0);
end
You can use linear indexing:
a (:,:,1) = [1 2 0; 2 0 1; 0 0 2];
a (:,:,2) = [0 2 8; 2 1 0; 0 0 0];
% Answer in linear indexing
idx = find(a == 0);
% Transforms linear indexing in rows-columns-3rd dimension
[rows , cols , third] = ind2sub(size(a) ,idx)
More on the topic can be found in Matlab's help
Lets assume your Matrix has the format N-by-M-by-P.
In your case
N = 3;
M = 3;
P = 2;
This would mean that the maximum length of rows and coloms from your search (if all entries are zero) is N*M=9
So one possible solution would be
%alloc output
row=zeros(size(a,1)*size(a,2),size(a,3));
colum=row;
%loop over third dimension
n=size(a,3);
for i = 1 : n
[row_t colum_t] = find(a(:,:,i)==0);
%copy your current result depending on it's length
row(1:length(row_t),i)=row_t;
colum(1:length(colum_t),i)=colum_t;
end
However, when you past the result to the next function / script you have to keep in mind to operate on the non-zero elements.
I would go for the vectorized solution of Zep. As for bigger matrices a it is more memory efficient and I am sure it must be way faster.

How to vectorize double loop in Matlab?

y = 0;
for m = 0:variable
for n = 0:m
y = y + f(n,m);
end
end
I vectorized the inner loop this way,
y = 0;
for m = 0:variable
n = 0:m
y = y + f(n,m);
end
This resulted in around 60% speed increase for my code. How do I also vectorize the outer loop?
You are probably looking for the meshgrid function. It is designed to fill in the sort of m by n combinations that it looks like you need. For example:
>> m = 1:4;
>> n = 1:3;
>> [mGridValues, nGridValues] = meshgrid(m,n)
mGridValues =
1 2 3 4
1 2 3 4
1 2 3 4
nGridValues =
1 1 1 1
2 2 2 2
3 3 3 3
This is a little more complicated since your inner loop depends on the value of your outer loop. So you will need to mask out the undesired [n, m] pairs (see below).
Modifying the prototype code that you have provided, you would end up with something like this:
[mValues, nValues] = meshgrid(0:variable, 0:variable); %Start with a full combination of values
mask = mValues >= nValues; %Identify all values where m >= n
mValues = mValues(mask); % And then remove pairs which do not
nValues = nValues(mask); % meet this criteria
y = f(nValues, mValues ); %Perform whatever work you are performing here

Matlab: Find row indexes with common elements

I have a Matrix:
1 2 3
4 5 6
7 8 1
How may I use matlab to find this:
for 1st row: row3
for 2nd row: ---
for 3rd row: row1
I want to have row indices for each row witch have common elements.
Consider this
A = [1 2 3; %Matrix A is a bit different from yours for testing
4 5 6;
7 8 1;
1 2 7;
4 5 6];
[row col] =size(A)
answers = zeros(row,row); %matrix of answers,...
%(i,j) = 1 if row_i and row_j have an equal element
for i = 1:row
for j = i+1:row %analysis is performed accounting for
% symmetry constraint
C = bsxfun(#eq,A(i,:),A(j,:)'); %Tensor comparison
if( any(C(:)) ) %If some entry is non-zero you have equal elements
answers(i,j) = 1; %output
end
end
end
answers = answers + answers'; %symmetric
The output here is
answers =
0 0 1 1 0
0 0 0 0 1
1 0 0 1 0
1 0 1 0 0
0 1 0 0 0
of course the answers matrix is symmetric because your relation is.
The solution proposed by Acorbe can be quite slow if you have many rows and/or long rows. I have checked that in most cases the two solutions that I present below should be considerably faster. If your matrix contains just few different values (relative to the size of the matrix) then this should work pretty fast:
function rowMatches = find_row_matches(A)
% Returns a cell array with row matches for each row
c = unique(A);
matches = false(size(A,1), numel(c));
for i = 1:numel(c)
matches(:, i) = any(A == c(i), 2);
end
rowMatches = arrayfun(#(j) ...
find(any(matches(:, matches(j,:)),2)), 1:size(A,1), 'UniformOutput', false);
This other alternative might be faster when you have short rows, i.e. when size(A,2) is small:
function answers = find_answers(A)
% Returns an "answers" matrix like in Acorbe's solution
c = unique(A);
answers = false(size(A,1), size(A,1));
idx = 1:size(A,1);
for i = 1:numel(c)
I = any(A == c(i), 2);
uMatch = idx(I);
answers(uMatch, uMatch) = true;
isReady = all(A <= c(i), 2);
if any(isReady),
idx(isReady) = [];
A = A(~isReady,:);
end
end
Depending on what you are planning to do with this output it could be redundant to have a match for "3rd row: row1".
You already have this match earlier in your output in the form of "1st row: row3"