Reputation: 185
I have two big matrices and I would like to perform an intersect operation between each row of these matrices. I would get something like that:
a =
8 1 6
3 5 7
4 9 2
b =
8 3 4
1 5 9
6 7 2
intersect(a,b) =
8
5
2
An intersect done for all n rows of A and B not using a for loop. Is it possible?
Upvotes: 2
Views: 146
Reputation: 35094
Edit: this is a kludgy solution, with only apparent vectorization (but in reality, arrayfun
is a wrapper around a loop). See the other answer by Divakar for a proper vectorized answer.
If your intersections are guaranteed to have equal size in each row, you can use arrayfun
like this:
intersect_array_fun = @(A,B) arrayfun(@(k) intersect(A(k,:),B(k,:)), ...
(1:size(A,1)).');
row_intersects = intersect_array_fun(a,b);
This will give you the answer:
row_intersects =
8
5
2
Note that this will fail if your output from arrayfun
is non-uniform, i.e. if there are multiple sizes returned by intersect
for every row. The error looks like this:
Error using arrayfun
Non-scalar in Uniform output, at index 1, output 1.
Set 'UniformOutput' to false.
Error in @(A,B)arrayfun(@(k)intersect(A(k,:),B(k,:)),1:size(A,1))
We can expect this and do instead
intersect_array_fun = @(A,B) arrayfun(@(k) intersect(A(k,:),B(k,:)), ...
(1:size(A,1)).');
intersect_cell_fun = @(A,B) arrayfun(@(k) intersect(A(k,:),B(k,:)), ...
(1:size(A,1)).', ...
'uniformoutput',false);
try
row_intersects = intersect_array_fun(a,b);
catch
row_intersects = intersect_cell_fun(a,b);
end
This will catch the error, and tell arrayfun
to return cell
output in case there's a mismatch in the size of the intersects. For your above example the result is
row_intersects =
8
5
2
but if we change a(1,3)
to 4
, we get
row_intersects =
[1x2 double]
[ 5]
[ 2]
instead: a column cell array.
This brings us to my last point: your question is of vectorization. You want to achieve all this without a for loop, working simultaneously on every row. Well, my first example might have fooled you, but arrayfun
is just a wrapper for a loop, and is not more efficient than the equivalent loop. However, my latter example with mismatching row sizes suggests that what you're doing is inherently not vectorizable. Since the output possibly consists of varying-length vectors for each row, you probably can't avoid using a loop.
Upvotes: 3
Reputation: 221594
Here's a vectorized approach using broadcasting/bsxfun
-
b_mask = squeeze(any(bsxfun(@eq,a,permute(b,[1,3,2])),2));
bt = b.';
out = mat2cell(bt(b_mask'),sum(b_mask,2));
Sample run -
a =
8 1 6
3 5 7
4 9 2
b =
3 8 2 6 1
5 6 9 0 3
6 7 1 5 3
out{1} =
8
6
1
out{2} =
5
3
out{3} =
[]
Please note that this would not work for cases with duplicate numbers in a row in b
, as in those cases it would put out duplicated numbers from b
in the output. This is probably not the expected one, given that we are performing intersect
operation that gives unique numbers.
To make it work for duplicate elements, we need to modify the creation of b_mask
, keeping rest as it is. So, the modified version would look something like this -
matches0 = bsxfun(@eq,a,permute(b,[1,3,2]));
[~,maxidx] = max(matches0,[],3);
matches1 = bsxfun(@eq,permute(1:size(b,2),[1,3,2]),maxidx) & matches0;
b_mask = squeeze(any(matches1,2));
Sample run -
a =
8 8 1
3 5 7
4 9 2
b =
3 8 1 8 5
5 6 9 0 3
6 7 1 5 3
out{1} =
8
1
out{2} =
5
3
out{3} =
[]
Upvotes: 3