Reputation: 6290
I have the following matrix in Matlab:
M = [0 0 1
1 0 0
0 1 0
1 0 0
0 0 1];
Each row has exactly one 1. How can I (without looping) determine a column vector so that the first element is a 2 if there is a 1 in the second column, the second element is a 3 for a one in the third column etc.? The above example should turn into:
M = [ 3
1
2
1
3];
Upvotes: 5
Views: 236
Reputation: 112659
Yet another approach: use the second output of max
:
[~, result] = max(M.', [], 1);
Or, as suggested by @rayryeng, use max
along the second dimension instead of transposing M
:
[~, result] = max(M, [], 2);
For
M = [0 0 1
1 0 0
0 1 0
1 0 0
0 0 1];
this gives
result =
3 1 2 1 3
If M
contains more than one 1
in a given row, this will give the index of the first such 1
.
Upvotes: 2
Reputation: 65430
You can actually solve this with simple matrix multiplication.
result = M * (1:size(M, 2)).';
3
1
2
1
3
This works by multiplying your M x 3 matrix with a 3 x 1 array where the elements of the 3x1 are simply [1; 2; 3]
. Briefly, for each row of M
, element-wise multiplication is performed with the 3 x 1 array. Only the 1's in the row of M
will yield anything in the result. Then the result of this element-wise multiplication is summed. Because you only have one "1" per row, the result is going to be the column index where that 1 is located.
So for example for the first row of M
.
element_wise_multiplication = [0 0 1] .* [1 2 3]
[0, 0, 3]
sum(element_wise_multiplication)
3
Update
Based on the solutions provided by @reyryeng and @Luis below, I decided to run a comparison to see how the performance of the various methods compared.
To setup the test matrix (M
) I created a matrix of the form specified in the original question and varied the number of rows. Which column had the 1 was chosen randomly using randi([1 nCols], size(M, 1))
. Execution times were analyzed using timeit
.
When run using M
of type double
(MATLAB's default) you get the following execution times.
If M
is a logical
, then the matrix multiplication takes a hit due to the fact that it has to be converted to a numerical type prior to matrix multiplication, whereas the other two have a bit of a performance improvement.
Here is the test code that I used.
sizes = round(linspace(100, 100000, 100));
times = zeros(numel(sizes), 3);
for k = 1:numel(sizes)
M = generateM(sizes(k));
times(k,1) = timeit(@()M * (1:size(M, 2)).');
M = generateM(sizes(k));
times(k,2) = timeit(@()max(M, [], 2), 2);
M = generateM(sizes(k));
times(k,3) = timeit(@()find(M.'), 2);
end
figure
plot(range, times / 1000);
legend({'Multiplication', 'Max', 'Find'})
xlabel('Number of rows in M')
ylabel('Execution Time (ms)')
function M = generateM(nRows)
M = zeros(nRows, 3);
col = randi([1 size(M, 2)], 1, size(M, 1));
M(sub2ind(size(M), 1:numel(col), col)) = 1;
end
Upvotes: 5
Reputation: 104464
You can also abuse find
and observe the row positions of the transpose of M
. You have to transpose the matrix first as find
operates in column major order:
M = [0 0 1
1 0 0
0 1 0
1 0 0
0 0 1];
[out,~] = find(M.');
Not sure if this is faster than matrix multiplication though.
Upvotes: 2