I am having trouble getting legend entries for a scatter plot in matlab.
I should have four different entries for each combination of two colors and two shapes.
colormap jet
x = rand(1,30); %x data
y = rand(1,30); %y data
c = [1 2 2 1 1 1 1 2 2 1 1 1 1 1 2 2 1 1 1 2 2 1 1 1 1 1 2 2 1 1]; %color
s = [2 2 1 1 1 2 1 2 2 1 1 1 1 2 2 2 1 1 1 1 2 2 1 1 1 2 2 1 1 2]; %shape
%index data for each shape (s)
s1 = s == 1; %square
s2 = s == 2; %circle
xsq = x(s1);
ysq = y(s1);
csq = c(s1);
xcirc = x(s2);
ycirc = y(s2);
ccirc = c(s2);
%plot data with different colors and shapes
h1 = scatter(xsq, ysq, 50,csq,'s','jitter','on','jitterAmount',0.2);
hold on
h2 = scatter(xcirc, ycirc, 50, ccirc, 'o','jitter','on','jitterAmount',0.2);
This plots a scatter plot with red circles and squares and blue circles and squares. Now I want a legend (this doesn't work).
%legend for each combination
legend([h1(1) h1(2) h2(1) h2(2)],'red+square','red+circle','blue+square','blue+circle')
Any ideas? Thanks :)
scatter is very limited when you want to place more than one set of points together. I would use plot instead as you can chain multiple sets in one command. Once you do that, it's very easy to use legend. Do something like this:
colormap jet
x = rand(1,30); %x data
y = rand(1,30); %y data
c = [1 2 2 1 1 1 1 2 2 1 1 1 1 1 2 2 1 1 1 2 2 1 1 1 1 1 2 2 1 1]; %color
s = [2 2 1 1 1 2 1 2 2 1 1 1 1 2 2 2 1 1 1 1 2 2 1 1 1 2 2 1 1 2]; %shape
%index data for each shape (s)
s1 = s == 1; %square
s2 = s == 2; %circle
c1 = c == 1; %circle colour %// NEW
c2 = c == 2; %square colour %// NEW
red_squares = s1 & c1; %// NEW
blue_squares = s1 & c2; %// NEW
red_circles = s2 & c1; %// NEW
blue_circles = s2 & c2; %// NEW
plot(x(red_squares), y(red_squares), 'rs', x(blue_squares), y(blue_squares), 'bs', x(red_circles), y(red_circles), 'ro', x(blue_circles), y(blue_circles), 'bo');
legend('red+square','blue+square','red+circle','blue+circle');
What's important is this syntax:
red_squares = s1 & c1;
blue_squares = s1 & c2;
red_circles = s2 & c1;
blue_circles = s2 & c2;
This uses logical indexing so that we select those circles and squares that belong to one colour or another colour. In this case, we choose only those shapes that are square and that belong to the first colour. There are four different combinations:
s1, c1
s1, c2
s2, c1
s2, c2
We get:
Related
How to obtain the coordinates of the first and the last appearances (under column-major ordering) of each label present in a matrix?
Example of a label matrix (where labels are 1 to 4):
L = [
1 1 1 1 0 0 0 0
0 0 0 0 2 2 0 0
0 0 0 0 0 0 2 0
0 0 0 0 0 0 0 0
0 0 0 0 0 3 0 0
0 0 0 0 0 0 3 3
0 0 0 4 0 0 0 0
4 4 4 0 0 0 0 0
];
For the above example L, I would like to obtain a matrix of coordinates like:
M = [
1 1 1
1 4 1
2 5 2
3 7 2
5 6 3
6 8 3
8 1 4
7 4 4 ];
Where the 1st column of M contains horizontal coordinates, the 2nd contains vertical coordinates, and the 3rd column contains the label. There should be 2 rows for each label.
With for-loop you can do it like that:
M=zeros(2*max(L(:)),3);
for k=1:max(L(:))
[r,c]=find(L==k);
s=sortrows([r c],2);
M(k*2-1:k*2,:)=[s(1,:) k; s(end,:) k];
end
M =
1 1 1
1 4 1
2 5 2
3 7 2
5 6 3
6 8 3
8 1 4
7 4 4
Maybe somehow with regionprops options you can do it without the loop...
I just had to try it with accumarray:
R = size(L, 1);
[rowIndex, colIndex, values] = find(L); % Find nonzero values
index = (colIndex-1).*R+rowIndex; % Create a linear index
labels = unique(values); % Find unique values
nLabels = numel(labels);
minmax = zeros(2, nLabels);
minmax(1, :) = accumarray(values, index, [nLabels 1], #min); % Collect minima
minmax(2, :) = accumarray(values, index, [nLabels 1], #max); % Collect maxima
temp = ceil(minmax(:)/R);
M = [minmax(:)-R.*(temp-1) temp repelem(labels, 2, 1)]; % Convert index to subscripts
M =
1 1 1
1 4 1
2 5 2
3 7 2
5 6 3
6 8 3
8 1 4
7 4 4
Here's what I got for timing with Dev-iL's script and Adiel's newest code (Note that the number of labels can't go above 127 due to how Adiel's code uses the uint8 values as indices):
| Adiel | Dev-iL | gnovice
-----------------------+---------+---------+---------
20 labels, 1000x1000 | 0.0753 | 0.0991 | 0.0889
20 labels, 10000x10000 | 12.0010 | 10.2207 | 8.7034
120 labels, 1000x1000 | 0.1924 | 0.3439 | 0.1387
So, for moderate numbers of labels and (relatively) smaller sizes, Adiel's looping solution looks like it does best, with my solution lying between his and Dev-iL's. For larger sizes or greater numbers of labels, my solution starts to take the lead.
If you're looking for a vectorized solution, you can do this:
nTags = max(L(:));
whois = bsxfun(#eq,L,reshape(1:nTags,1,1,[]));
% whois = L == reshape(1:nTags,1,1,[]); % >=R2016b syntax.
[X,Y,Z] = ind2sub(size(whois), find(whois));
tmp = find(diff([0; Z; nTags+1])); tmp = reshape([tmp(1:end-1) tmp(2:end)-1].',[],1);
M = [X(tmp), Y(tmp), repelem(1:nTags,2).'];
Or with extreme variable reuse:
nTags = max(L(:));
Z = bsxfun(#eq,L,reshape(1:nTags,1,1,[]));
[X,Y,Z] = ind2sub(size(Z), find(Z));
Z = find(diff([0; Z; nTags+1]));
Z = reshape([Z(1:end-1) Z(2:end)-1].',[],1);
M = [X(Z), Y(Z), repelem(1:nTags,2).'];
Here's my benchmarking code:
function varargout = b42973322(isGPU,nLabels,lMat)
if nargin < 3
lMat = 1000;
end
if nargin < 2
nLabels = 20; % if nLabels > intmax('uint8'), Change the type of L to some other uint.
end
if nargin < 1
isGPU = false;
end
%% Create L:
if isGPU
L = sort(gpuArray.randi(nLabels,lMat,lMat,'uint8'),2);
else
L = sort(randi(nLabels,lMat,lMat,'uint8'),2);
end
%% Equality test:
M{3} = DeviL2(L);
M{2} = DeviL1(L);
M{1} = Adiel(L);
assert(isequal(M{1},M{2},M{3}));
%% Timing:
% t(3) = timeit(#()DeviL2(L)); % This is always slower, so it's irrelevant.
t(2) = timeit(#()DeviL1(L));
t(1) = timeit(#()Adiel(L));
%% Output / Print
if nargout == 0
disp(t);
else
varargout{1} = t;
end
end
function M = Adiel(L)
M=[];
for k=1:max(L(:))
[r,c]=find(L==k);
s=sortrows([r c],2);
M=[M;s(1,:) k; s(end,:) k];
end
end
function M = DeviL1(L)
nTags = max(L(:));
whois = L == reshape(1:nTags,1,1,[]); % >=R2016b syntax.
[X,Y,Z] = ind2sub(size(whois), find(whois));
tmp = find(diff([0; Z; nTags+1])); tmp = reshape([tmp(1:end-1) tmp(2:end)-1].',[],1);
M = [X(tmp), Y(tmp), repelem(1:nTags,2).'];
end
function M = DeviL2(L)
nTags = max(L(:));
Z = L == reshape(1:nTags,1,1,[]);
[X,Y,Z] = ind2sub(size(Z), find(Z));
Z = find(diff([0; Z; nTags+1]));
Z = reshape([Z(1:end-1) Z(2:end)-1].',[],1);
M = [X(Z), Y(Z), repelem(1:nTags,2).'];
end
You can retrive the uniqe values (your labels) of the matrix with unique.
Having them retrived you can use find to get their indices.
Put together your matrix with it.
I have a binary matrix A of dimension mxn with m>n in Matlab. I want to construct a matrix B of dimension cxn listing row wise each element of the Cartesian product of the row indices of the ones contained in A. To be more clear consider the following example.
Example:
%m=4;
%n=3;
A=[1 0 1;
0 0 1;
1 1 0;
0 0 1];
%column 1: "1" are at rows {1,3}
%column 2: "1" are at row {3}
%column 3: "1" are at rows {1,2,4}
%Hence, the Cartesian product {1,3}x{3}x{1,2,4} is
%{(1,3,1),(1,3,2),(1,3,4),(3,3,1),(3,3,2),(3,3,4)}
%I construct B by disposing row-wise each 3-tuple in the Cartesian product
%c=6
B=[1 3 1;
1 3 2;
1 3 4;
3 3 1;
3 3 2;
3 3 4];
You can get the cartesian product with the combvec command, for your example:
A=[1 0 1;...
0 0 1;...
1 1 0;...
0 0 1];
[x y]=find(A);
B=combvec(x(y==1).',x(y==2).',x(y==3).').';
% B =
% 1 3 1
% 3 3 1
% 1 3 2
% 3 3 2
% 1 3 4
% 3 3 4
You can expand this to an unknown number of columns by using the associative property of the product.
[x y]=find(A);
u_y=unique(y);
B=x(y==u_y(1)).';
for i=2:length(u_y)
B=combvec(B, x(y==u_y(i)).');
end
B=B.';
One solution (without toolbox):
A= [1 0 1;
0 0 1;
1 1 0;
0 0 1];
[ii,jj] = find(A)
kk = unique(jj);
for i = 1:length(kk)
v{i} = ii(jj==kk(i));
end
t=cell(1,length(kk));
[t{:}]= ndgrid(v{:});
product = []
for i = 1:length(kk)
product = [product,t{i}(:)];
end
You can use use accumarray to obtain vectors with the row indices of nonzero elements for each column. This works for an arbitrary number of columns:
[ii, jj] = find(A);
vectors = accumarray(jj, ii, [], #(x){sort(x.')});
Then apply this answer to efficiently compute the Cartesian product of those vectors:
n = numel(vectors);
B = cell(1,n);
[B{end:-1:1}] = ndgrid(vectors{end:-1:1});
B = cat(n+1, B{:});
B = reshape(B,[],n);
In your example, this gives
B =
1 3 1
1 3 2
1 3 4
3 3 1
3 3 2
3 3 4
In short, I would use find to generate the indices needed for the Cartesian product and then use ndgrid to perform the Cartesian product of these indices. The code to do so is:
clear
close all
clc
A = [1 0 1;
0 0 1;
1 1 0;
0 0 1];
[row,col] = find(A);
[~,ia,~] = unique(col);
n_cols = size(A,2);
indices = cell(n_cols,1);
for ii = 1:n_cols-1
indices{ii} = row(ia(ii):ia(ii+1)-1);
end
indices{end} = row(ia(end):end);
cp_temp = cell(n_cols,1);
[cp_temp{:}] = ndgrid(indices{:});
cp = NaN(numel(cp_temp{1}),n_cols);
for ii = 1:n_cols
cp(:,ii) = cp_temp{ii}(:);
end
cp = sortrows(cp);
cp
I have a list of numbers, [1:9], that I need to divide three groups. Each group must contain at least one number. I need to enumerate all of the combinations (i.e. order does not matter). Ideally, the output is a x by 3 array. Any ideas of how to do this in matlab?
Is this what you want:
x = 1:9;
n = length(x);
T=3;
out = {};
%// Loop over all possible solutions
for k=1:T^n
s = dec2base(k, T, n);
out{k}{T} = [];
for p=1:n
grpIndex = str2num(s(p))+1;
out{k}{grpIndex} = [out{k}{grpIndex} x(p)];
end
end
%// Print result. size of out is the number of ways to divide the input. out{k} contains 3 arrays with the values of x
out
Maybe this is what you want. I'm assuming that the division in groups is "monotonous", that is, first come the elements of the first group, then those of the second etc.
n = 9; %// how many numbers
k = 3; %// how many groups
b = nchoosek(1:n-1,k-1).'; %'// "breaking" points
c = diff([ zeros(1,size(b,2)); b; n*ones(1,size(b,2)) ]); %// result
Each column of c gives the sizes of the k groups:
c =
Columns 1 through 23
1 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 4 4 4 4 5
1 2 3 4 5 6 7 1 2 3 4 5 6 1 2 3 4 5 1 2 3 4 1
7 6 5 4 3 2 1 6 5 4 3 2 1 5 4 3 2 1 4 3 2 1 3
Columns 24 through 28
5 5 6 6 7
2 3 1 2 1
2 1 2 1 1
This produces what I was looking for. The function nchoosekr_rec() is shown below as well.
for x=1:7
numgroups(x)=x;
end
c=nchoosekr_rec(numgroups,modules);
i=1;
d=zeros(1,modules);
for x=1:length(c(:,1))
c(x,modules+1)=sum(c(x,1:modules));
if c(x,modules+1)==length(opt_mods)
d(i,:)=c(x,1:modules);
i=i+1;
end
end
numgroups=[];
for x=1:length(opt_mods)
numgroups(x)=x;
end
count=0;
for x=1:length(d(:,1))
combos=combnk(numgroups,d(x,1));
for y=1:length(combos(:,1))
for z=1:nchoosek(9-d(x,1),d(x,2))
new_mods{count+z,1}=combos(y,:);
numgroups_temp{count+z,1}=setdiff(numgroups,new_mods{count+z,1});
end
count=count+nchoosek(9-d(x,1),d(x,2));
end
end
count=0;
for x=1:length(d(:,1))
for y=1:nchoosek(9,d(x,1))
combos=combnk(numgroups_temp{count+1},d(x,2));
for z=1:length(combos(:,1))
new_mods{count+z,2}=combos(z,:);
new_mods{count+z,3}=setdiff(numgroups_temp{count+z,1},new_mods{count+z,2});
end
count=count+length(combos(:,1));
end
end
function y = nchoosekr_rec(v, n)
if n == 1
y = v;
else
v = v(:);
y = [];
m = length(v);
if m == 1
y = zeros(1, n);
y(:) = v;
else
for i = 1 : m
y_recr = nchoosekr_rec(v(i:end), n-1);
s_repl = zeros(size(y_recr, 1), 1);
s_repl(:) = v(i);
y = [ y ; s_repl, y_recr ];
end
end
end
If there is a matrix and two known points, how to create one random path (no need to be the shortest) between these two points with:
a path that can have a level of deviation
a path can be totally random (but not necessary)
the next step can be only from 4 neighbors
ex.
5x5 matrix with two known points: (2,1) and (5,5)
After input: pt1 = [2,1]; pt2 = [5,5];.
How could I get the pattern such as follows with the path recorded in the parameter, such aspath = [2,1;2,2-;3,2;4,2;4,3;4,4;4,5;5,5].
X X X X X
o o X X X
X o X X X
X o o o o
X X X X o
PART A - Aim is to find coordinates of a line/path connecting two points on a 2D domain such that no two neighboring coordinates are diagonal to each other i.e. that is left/right/top/bottom only.
Function codes
function pts_array = points_array(pt1,pt2)
if pt1(1)==pt2(1)
if pt2(2)>pt1(2)
pts_array = [repmat(pt1(1),(pt2(2)-pt1(2)+1),1) (pt1(2):pt2(2))'];
elseif pt2(2)<pt1(2)
pts_array = flipud([repmat(pt1(1),(pt1(2)-pt2(2)+1),1) (pt2(2):pt1(2))']);
else
pts_array = pt1;
end
elseif pt1(2)==pt2(2)
if pt2(1)>pt1(1)
pts_array = [(pt1(1):pt2(1))' repmat(pt1(2),(pt2(1)-pt1(1)+1),1)];
elseif pt2(1)<pt1(1)
pts_array = flipud([(pt2(1):pt1(1))' repmat(pt1(2),(pt1(1)-pt2(1)+1),1)]);
else
pts_array = pt1;
end
else
gslope1_org = (pt2(2)-pt1(2))/(pt2(1)-pt1(1));
if gslope1_org <1
pt1 = fliplr(pt1);
pt2 = fliplr(pt2);
end
gslope1 = (pt2(2)-pt1(2))/(pt2(1)-pt1(1));
off1 = 1;
pts_array = [pt1];
gpt1 = pt1;
while 1
slope1 = (pt2(2)-gpt1(2))/(pt2(1)-gpt1(1));
if (slope1<gslope1)
gpt1 = [gpt1(1)+off1 gpt1(2)];
pts_array = [pts_array; gpt1];
else
new_y = floor(gpt1(2)+slope1);
range_y = (gpt1(2)+1 : floor(gpt1(2)+slope1))';
gpt1 = [gpt1(1) new_y];
pts_array = [pts_array ; [repmat(gpt1(1),[numel(range_y) 1]) range_y]];
end
if isequal(gpt1,pt2)
break;
end
end
if gslope1_org <1
pts_array = fliplr(pts_array);
end
end
function pts_array = points_array_wrap(pt1,pt2) %%// Please remember that this needs points_array.m
x1 = pt1(1);
y1 = pt1(2);
x2 = pt2(1);
y2 = pt2(2);
quad4 = y2<y1 & x2>x1; %% when pt2 is a lower height than pt1 on -slope
quad3 = y2<y1 & x2<x1; %% when pt2 is a lower height than pt1 on +slope
quad2 = y2>y1 & x2<x1; %% when pt2 is a higher height than pt1 on -slope
if quad4
y2 = y2+ 2*(y1 - y2);
end
if quad2
y2 = y2 - 2*(y2 - y1);
t1 = x1;t2 = y1;
x1 = x2;y1 = y2;
x2 = t1;y2 = t2;
end
if quad3
t1 = x1;t2 = y1;
x1 = x2;y1 = y2;
x2 = t1;y2 = t2;
end
pts_array = points_array([x1 y1],[x2 y2]);
if quad4
offset_mat = 2.*(pts_array(:,2)-pt1(2));
pts_array(:,2) = pts_array(:,2) - offset_mat;
end
if quad3
pts_array = flipud(pts_array);
end
if quad2
offset_mat = 2.*(pt1(2)-pts_array(:,2));
pts_array(:,2) = pts_array(:,2) + offset_mat;
pts_array = flipud(pts_array);
end
return;
Script
pt1 = [2 1];
pt2 = [5 5];
pts_array = points_array_wrap(pt1,pt2);
plot(pts_array(:,1),pts_array(:,2),'o'), grid on, axis equal
for k = 1:size(pts_array,1)
text(pts_array(k,1),pts_array(k,2),strcat('[',num2str(pts_array(k,1)),',',num2str(pts_array(k,2)),']'),'FontSize',16)
end
Output
pts_array =
2 1
2 2
3 2
3 3
4 3
4 4
4 5
5 5
Plot
PART B - Aim is to find coordinates of a line/path connecting two points on a 2D domain through given spaces.
In this special case, we are assuming that there are some spaces and only through which the path is to be connected. This is not asked by OP, but I thought it could interesting to share. So, for this, the spaces would be the o's as shown in OP's question.
Code
function your_path = path_calc(mat1,starting_pt,final_pt)
[x1,y1] = find(mat1);
pt1 = [x1 y1];
d1 = pdist2(pt1,final_pt,'euclidean');
[~,ind1] = sort(d1,'descend');
path1 = pt1(ind1,:);
your_path = path1(find(ismember(path1,starting_pt,'rows')):end,:);
return;
Run - 1
%%// Data
mat1 = zeros(5,5);
mat1(2,1:2) = 1;
mat1(3,2) = 1;
mat1(4,2:5) = 1;
mat1(5,5) = 1;
starting_pt = [2 1];
final_pt = [5 5];
%%// Path traces
path = path_calc(mat1,starting_pt,final_pt);
Gives -
mat1 =
0 0 0 0 0
1 1 0 0 0
0 1 0 0 0
0 1 1 1 1
0 0 0 0 1
path =
2 1
2 2
3 2
4 2
4 3
4 4
4 5
5 5
Run - 2
%%// Data
mat1 = zeros(5,5);
mat1(2,1:2) = 1;
mat1(3,2) = 1;
mat1(4,2:5) = 1;
mat1(5,5) = 1;
mat1 = fliplr(mat1');
%%// Notice it starts not from the farthest point this time
starting_pt = [2 3];
final_pt = [5 1];
%%// Path traces
path = path_calc(mat1,starting_pt,final_pt);
Gives
mat1 =
0 0 0 1 0
0 1 1 1 0
0 1 0 0 0
0 1 0 0 0
1 1 0 0 0
path =
2 3
2 2
3 2
4 2
5 2
5 1
To find a purely random path from the start to the goal, this function selects a random direction, checks that there is a valid neighbor in that direction and if there is, moves to that new neighbor and adds it to the path.
Directions can be invalid if, for instance, we're in the leftmost column and try to move left. We could check beforehand and only select randomized directions that lead to valid neighbors, but that would complicate the code and the chances of selecting a valid neighbor are at worst 50/50.
function path = random_path(start, goal, board_size)
m = board_size(1);
n = board_size(2);
isInBounds = #(x) x(1) >= 1 && x(1) <= m && x(2) >= 1 && x(2) <= n;
neighbor_offset = [ 0, -1; % Neighbor indices:
-1, 0; % 2
0, 1; % 1 x 3
1, 0]; % 4
% Edit: get the actual size of our neighbor list
[possible_moves, ~] = size(neighbor_offset);
current_position = start;
path = current_position;
while sum(current_position ~= goal) > 0
valid = false;
while ~valid
% Edit: "magic numbers" are bad; fixed below
% move = randi(4);
move = randi(possible_moves);
candidate = current_position + neighbor_offset(move, :);
valid = isInBounds(candidate);
end
current_position = candidate;
path = [path; current_position];
end
end
The while condition:
sum(current_position ~= goal) > 0
continues while at least one of the coordinates of sum and goal are different. I'm sure this could be written more concisely, so if there are any suggestions as to how to improve this I'd be grateful.
Likewise, the isInBounds anonymous function also seems a bit clunky, so any suggestions there would be appreciated as well.
At any rate, here's a sample of the output. Since the paths are completely random, some of them can get quite long:
random_path([2,1], [5,5], [5,5])
ans =
2 1
3 1
2 1
3 1
3 2
4 2
4 1
5 1
4 1
4 2
3 2
3 1
2 1
1 1
2 1
3 1
3 2
4 2
4 3
4 4
4 3
4 2
4 3
5 3
4 3
3 3
4 3
4 2
4 1
4 2
4 1
4 2
4 3
4 2
5 2
5 3
5 2
4 2
3 2
3 3
3 4
3 5
3 4
2 4
3 4
4 4
5 4
5 3
4 3
3 3
3 2
4 2
4 3
4 4
5 4
5 5
I need to generate (I prefere MATLAB) all "unique" integer tuples k = (k_1, k_2, ..., k_r) and
its corresponding multiplicities, satisfying two additional conditions:
1. sum(k) = n
2. 0<=k_i<=w_i, where vector w = (w_1,w_2, ..., w_r) contains predefined limits w_i.
"Unique" tuples means, that it contains unique unordered set of elements
(k_1,k_2, ..., k_r)
[t,m] = func(n,w)
t ... matrix of tuples, m .. vector of tuples multiplicities
Typical problem dimensions are about:
n ~ 30, n <= sum(w) <= n+10, 5 <= r <= n
(I hope that exist any polynomial time algorithm!!!)
Example:
n = 8, w = (2,2,2,2,2), r = length(w)
[t,m] = func(n,w)
t =
2 2 2 2 0
2 2 2 1 1
m =
5
10
in this case exist only two "unique" tuples:
(2,2,2,2,0) with multiplicity 5
there are 5 "identical" tuples with same set of elements
0 2 2 2 2
2 0 2 2 2
2 2 0 2 2
2 2 2 0 2
2 2 2 2 0
and
(2,2,2,1,1) with multiplicity 10
there are 10 "identical" tuples with same set of elements
1 1 2 2 2
1 2 1 2 2
1 2 2 1 2
1 2 2 2 1
2 1 1 2 2
2 1 2 1 2
2 1 2 2 1
2 2 1 1 2
2 2 1 2 1
2 2 2 1 1
Thanks in advance for any help.
Very rough (extremely ineffective) solution. FOR cycle over 2^nvec-1 (nvec = r*maxw) test samples and storage of variable res are really terrible things!!!
This solution is based on tho following question.
Is there any more effective way?
function [tup,mul] = tupmul(n,w)
r = length(w);
maxw = max(w);
w = repmat(w,1,maxw+1);
vec = 0:maxw;
vec = repmat(vec',1,r);
vec = reshape(vec',1,r*(maxw+1));
nvec = length(vec);
res = [];
for i = 1:(2^nvec - 1)
ndx = dec2bin(i,nvec) == '1';
if sum(vec(ndx)) == n && all(vec(ndx)<=w(ndx)) && length(vec(ndx))==r
res = [res; vec(ndx)];
end
end
tup = unique(res,'rows');
ntup = size(tup,1);
mul = zeros(ntup,1);
for i=1:ntup
mul(i) = size(unique(perms(tup(i,:)),'rows'),1);
end
end
Example:
> [tup mul] = tupmul(8,[2 2 2 2 2])
tup =
0 2 2 2 2
1 1 2 2 2
mul =
5
10
Or same case but with changed limits for first two positions:
>> [tup mul] = tupmul(8,[1 1 2 2 2])
tup =
1 1 2 2 2
mul =
10
This is far more better algorithm, created by Bruno Luong (phenomenal MATLAB programmer):
function [t, m, v] = tupmul(n, w)
v = tmr(length(w), n, w);
t = sort(v,2);
[t,~,J] = unique(t,'rows');
m = accumarray(J(:),1);
end % tupmul
function v = tmr(p, n, w, head)
if p==1
if n <= w(end)
v = n;
else
v = zeros(0,1);
end
else
jmax = min(n,w(end-p+1));
v = cell2mat(arrayfun(#(j) tmr(p-1, n-j, w, j), (0:jmax)', ...
'UniformOutput', false));
end
if nargin>=4 % add a head column
v = [head+zeros(size(v,1),1,class(head)) v];
end
end %tmr