cumsum only within groups? - matlab

Lets say I have 2 vectors:
a=[0 1 0 1 1 0 1 0 0 0 1 1 1];
b=[1 1 1 1 1 1 2 2 2 3 3 3 3];
For every group of numbers in b I want to cumsum, so that the result should look like that:
c=[1 3;2 1;3 3]
That means that I have for the ones in b 3 ones in a, for group two in b I have only one one in a etc.

There have been some complicated answers so far. Try accumarray(b',a').

If you're looking for a solution where b can be anything, then a combination of hist and unique will help:
num = unique(b(logical(a))); %# identify the numbers in b with non-zero counts
cts = hist(b(logical(a)),num); %# count
c = [num(:),cts(:)]; %# combine.
If you want the first column of c to go from 1 to the maximum of b, then you can rewrite the first line as num=1:max(b), and you'll also get rows in c where the counts are zero.

Assuming that b is monotonically increasing by 1:
c = cell2mat(transpose(arrayfun( #(x) [ x sum(a(find( b == x ))) ], min(b):max(b), 'UniformOutput',false)))
should give the right answer in a one liner format, or:
for ii=min(b):max(b)
II = find( b == ii );
v = sum(a(II));
c(ii,:) = [ii v];
end
which is a bit easier to read. Hope this helps.

Related

Find all subsets of a finite metric space for which the sum of distances is less than a given number

I have five elements A, B, C, D and E.
The distance between each of the elements is given by the matrix below:
Distances =
[0 5 3 8 15;
5 0 7 5 20;
3 7 0 12 12;
8 5 12 0 8;
7 20 12 8 0]
I want to choose all combinations of elements such that the sum of distances is less than 10.
It can be done recursively by:
First find sets of 2-item eligible combinations.
Then, find sets of 3-item eligible combinations by adding another item to the previously-found eligible 2-item combinations.
Etc.
Doing it by hand for the above example I get the following combinations:
A,
B,
C,
D,
E,
A B,
A C,
A D,
B C,
B D,
D E,
A B C
How would I do this systematically in Octave, if the number of elements is large (say 250)?
Several general points:
Since the original question was tagged with matlab, I will show a solution which I tested there.
This solution uses the functions VChooseK and VChooseKRO found on FEX, which need to be compiled into MEX using an appropriate compiler.
Even though the question talks about distances, and there's little sense in adding up discontinuous paths (i.e. A->C + B->D), since this is not specified explicitly in the question as something invalid, the solution below outputs them as well.
The solution is shown for the example given in the OP. It should be modified slightly to output readable results for 250 nodes, (i.e. change the node "names" from letters to numbers seeing how 26 < 250).
Outputs are currently only printed. Some modifications need to be made (in the form of temporary variables) if further computations are required on the result.
function q41308927
%% Initialization:
nodes = char((0:4) + 'A');
D = [0 5 3 8 15;
5 0 7 5 20;
3 7 0 12 12;
8 5 12 0 8;
7 20 12 8 0];
thresh = 10;
d = triu(D); % The problem is symmetric (undirected), so we only consider the upper half.
% Also keep track of the "letter form":
C = reshape(cellstr(VChooseKRO(nodes,2)), size(D)).'; % "C" for "Combinations"
%{
C =
5×5 cell array
'AA' 'AB' 'AC' 'AD' 'AE'
'BA' 'BB' 'BC' 'BD' 'BE'
'CA' 'CB' 'CC' 'CD' 'CE'
'DA' 'DB' 'DC' 'DD' 'DE'
'EA' 'EB' 'EC' 'ED' 'EE'
%}
C = C(d>0); d = d(d>0);
assert(numel(C) == numel(d)); % This is important to check
%% Find eligible sets of size n
for k = 1:numel(nodes)
if numel(d)<k
break
end
% Enumerate combinations:
C = C(VChooseK(1:numel(C),k));
d = sum(VChooseK(d,k),2);
% Filter combinations:
if any(d < thresh)
C(d >= thresh,:) = [];
d = d(d < thresh);
disp(sortrows(C)); % This is just to show it works like the manual example
else
break
end
end
The output of the above is:
'AB'
'AC'
'AD'
'BC'
'BD'
'DE'
'AB' 'AC'
'AC' 'BD'
This is a plain Octave (or Matlab) solution. The matrix Distances is as in the question. The algorithm builds a 0-1 matrix a in which each column encodes a set with sum of distances less than limit (for example 10).
The matrix a is initialized with identity, because all one-elements subsets are admissible (sum of distances is 0). Then each column is picked c = a(:,m); and the possibility of adding another element is investigated (cand = c; cand(k) = 1; means adding k-th element). Without loss of generality, it is enough to consider adding only the elements after the last current element of the set.
The product cand'*Distances*cand is twice the sum of distances of the candidate set cand. So, if it's less than twice the limit, the column is added: a = [a cand];. At the end, the matrix a is displayed in transposed form, for readability.
limit = 10;
n = length(Distances);
a = eye(n, n);
col1 = 1;
col2 = n;
while (col1 <= col2)
for m = col1:col2
c = a(:,m);
for k = max(find(c>0))+1:n
cand = c;
cand(k) = 1;
if cand'*Distances*cand < 2*limit
a = [a cand];
end
end
end
col1 = col2 + 1;
col2 = length(a);
end
disp(a')
Output:
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
1 1 0 0 0
1 0 1 0 0
1 0 0 1 0
0 1 1 0 0
0 1 0 1 0
0 0 0 1 1
With a 250 by 250 matrix, the performance will greatly depend on how large limit is. If it is so large that all or most of 2^250 sets qualify, this is going to run out of memory. (2^250 is more than 10^75, so I don't think you'd ever want to see anywhere near that many sets).
And this is to have output in a more readable form:
for m = 1:length(a)
disp(find(a(:,m))')
end
Output:
1
2
3
4
5
1 2
1 3
1 4
2 3
2 4
4 5

How to concatenate matrices horizontally?

I have two 3-by-3 matrices:
A= [ 1 2 3
1 1 1
0 1 1]
B= [ 1 2 1
1 1 1
2 2 2]
How do I concatenate the A and B matrices to create the concat matrix as seen below?
concat= [1 2 3 1 2 1
1 1 1 1 1 1
0 1 1 2 2 2]
Simply do:
concat = [A B];
This will make a new matrix that pieces A and B together horizontally (i.e. concatenates).
Another possibility is to use cat where you specify the second dimension (column-wise) to concatenate the two matrices together.
concat = cat(2, A, B);
Alternatively you can use horzcat as alluded by a few people here. This essentially is syntactic sugar for cat in the second dimension.
concat = horzcat(A, B);
There are a few possibilities here. The easiest, and most common one:
concat = [A, B]
The following is considered more robust by some, (because one might, on accident, do concat = [A; B], which would concatenate them vertically):
concat = horzcat(A, B)

cumsum of values for same timeunit

i have the following vectors:
A=[1 0 1 0 0 1 0 1 0 0];
B=[1 2 3 4 5 6 7 8 9 10];
in this case A represents a time vector, where the 1s signal the beginning of one time unit.
now i want to add up all the values in B which correspond to a time unit with the same length of 3 steps.
So in this example this would mean the 3rd, 4th and 5th value and the 8th, 9th and 10th value of B should be summed cause these are in a time unit of length 3.
B_result=[12 27];
i know cumsum() is the command for this but i dont know how to say that only these specific values depending on the time indices of A should be summed.
can you help me?
thanks alot
You can use cumsum alongside accumarray and hist:
csa = cumsum(A); %// from begining og unit to unit indices
n = hist(csa, 1:max(csa)); %// count num of steps in each unit
B_result = accumarray( csa', B' ); %// accumulate B into different time units
B_result(n~=3) = []; %// discard all time units that do not have 3 steps
For a simpler pattern matching, you can use strfind:
loc = strfind([A,1],[1 0 0 1]); %// add the 1 at the end of A and the pattern to avoid longer intervals
idx = bsxfun(#plus,loc,(0:2)'); %'// get the indices that need to be summed
result = sum(B(idx),1); %// obtain the result
N = 3; %// We want to detect a one followed by exactly N-1 zeros. Call that
%// sequence an "interesting part"
ind = find([A 1]); %// find ones. Append a last one to detect a possible
%// interesting part at the end.
ind = ind(diff(ind)==N); %// index of beginning of interesting parts
cs = cumsum(B); %// accumulate values
B_result = cs(ind+N-1)-cs(ind-1); %// use index to build result
A more generic application of Jonas' Idea:
A = [1 0 1 0 0 1 0 1 0 0 0 0 1];
B = [1 2 3 4 5 6 7 8 9 10 11 12];
n = 3;
result = arrayfun(#(x) sum( B(x:x+n-1) ), strfind([A,1],num2str(10^n+1)-48))
or use cumsum instead of sum, I was not sure what you actually want:
result = arrayfun(#(x) cumsum( B(x:x+n-1) ), ...
strfind( [A,1],num2str(10^n+1)-48 ) ,'uni',0)
%optional:
result = cell2mat(result')

matlab indexing with multiple condition

I can't figure out how to create a vector based on condition on more than one other vectors. I have three vectors and I need values of one vector if values on other vectors comply to condition.
As an example below I would like to choose values from vector a if values on vector b==2 and values on vector c==0 obviously I expect [2 4]
a = [1 2 3 4 5 6 7 8 9 10];
b = [1 2 1 2 1 2 1 2 1 2];
c = [0 0 0 0 0 1 1 1 1 1]
I thought something like:
d = a(b==2) & a(c==0)
but I have d = 1 1 1 1 1 not sure why.
It seems to be basic problem but I can find solution for it.
In your case you can consider using a(b==2 & c==0)
Use ismember to find the matching indices along the rows after concatenating b and c and then index to a.
Code
a(ismember([b;c]',[2 0],'rows'))
Output
ans =
2
4
You may use bsxfun too for the same result -
a(all(bsxfun(#eq,[b;c],[2 0]'),1))
Or you may just tweak your method to get the correct result -
a(b==2 & c==0)

How to shift zero in the last column of a matrix

I have one matrix like below-
A=[1 1 1 1 1;
0 1 1 1 2;
0 0 1 1 3]
But I want to place all the 0 at the end of the row, so A should be like-
A=[1 1 1 1 1;
1 1 1 2 0;
1 1 3 0 0]
How can I do this? Matlab experts please help me.
There you go. Whole matrix, no loops, works even for non-contiguous zeros:
A = [1 1 1 1 1; 0 1 1 1 2; 0 0 1 1 3];
At = A.'; %// It's easier to work with the transpose
[~, rows] = sort(At~=0,'descend'); %// This is the important part.
%// It sends the zeros to the end of each column
cols = repmat(1:size(At,2),size(At,1),1);
ind = sub2ind(size(At),rows(:),cols(:));
sol = repmat(NaN,size(At,1),size(At,2));
sol(:) = At(ind);
sol = sol.'; %'// undo transpose
As usual, for Matlab versions that do not support the ~ symbol on function return, change ~ by a dummy variable, for example:
[nada, rows] = sort(At~=0,'descend'); %// This is the important part.
A more generic example:
A = [1 3 0 1 1;
0 1 1 1 2;
0 0 1 1 3]
% Sort columns directly
[~,srtcol] = sort(A == 0,2);
% Sorted positions
sz = size(A);
pos = bsxfun(#plus, (srtcol-1)*sz(1), (1:sz(1))'); % or use sub2ind
The result
B = A(pos)
B =
1 3 1 1 0
1 1 1 2 0
1 1 3 0 0
there are many ways to do this. one fast way can be easily like this:
a = [1 2 3 4 0 5 7 0];
idx=(find(a==0));
idx =
5 8
b=a; % save a new copy of the vector
b(idx)=[]; % remove zero elements
b =
1 2 3 4 5 7
c=[b zeros(size(idx))]
c =
1 2 3 4 5 7 0 0
You may modify this code as well.
If your zeros are always together, you could use the circshift command. This shifts values in an array by a specified number of places, and wraps values that run off the edge over to the other side. It looks like you would need to do this separately for each row in A, so in your example above you could try:
A(2,:) = circshift(A(2,:), [1 -1]); % shift the second row one to the left with wrapping
A(3,:) = circshift(A(3,:), [1 -2]); % shift the third row two to the left with wrapping
In general, if your zeros are always at the front of the row in A, you could try something like:
for ii = 1:size(A,1) % iterate over rows in A
numShift = numel(find(A(ii,:) == 0)); % assuming zeros at the front of the row, this is how many times we have to shift the row.
A(ii,:) = circshift(A(ii,:), [1 -numShift]); % shift it
end
Try this (just a fast hack):
for row_k = 1:size(A, 1)
[A_sorted, A_sortmap] = sort(A(row_k, :) == 0, 'ascend');
% update row in A:
A(row_k, :) = A(row_k, A_sortmap);
end
Now optimized for versions of MATLAB not supporting ~ as garbage lhs-identifier.
#LuisMendo's answer is inspiring in its elegance, but I couldn't get it to work (perhaps a matlab version thing). The following (based on his answer) worked for me:
Aaux = fliplr(reshape([1:numel(A)],size(A)));
Aaux(find(A==0))=0;
[Asort iso]=sort(Aaux.',1,'descend');
iso = iso + repmat([0:size(A,1)-1]*size(A,2),size(A,2),1);
A=A.';
A(iso).'
I've also asked this question and got a super elegant answer (non of above answers is same) here:
Optimize deleting matrix leading zeros in MATLAB