user3077261
user3077261

Reputation: 131

Finding all indices by ismember

This is what is described in one of the examples for ismember:

Define two vectors with values in common.

A = [5 3 4 2]; B = [2 4 4 4 6 8];

Determine which elements of A are also in B as well as their corresponding locations in B.

[Lia,Locb] = ismember(A,B)

The result is:

Lia =
     0     0     1     1

Locb =    
     0     0     2     1

The element in B with the lowest index that matches A(3) is B(2). A(4) equals B(1). Is there a way by which we could find all the indices of the elemets of B matching the same element in A?

Upvotes: 11

Views: 10462

Answers (5)

chappjc
chappjc

Reputation: 30579

The most elegant solutions (i.e. those without using iterations of find) involve swapping the inputs to ismember and grouping like indexes with accumarray, as in Eitan's answer, or vectorizing the find with bsxfun as in Luis Mendo's answer, IMHO.

However, for those interested in a solution with undocumented functionality, and an admittedly hackish approach, here is another way to do it (i.e. for each element of A, find the indexes of all corresponding elements in B). The thinking goes as follows: In a sorted B, what if you had the first and last indexes of each matching element? It turns out there are two helper functions used by ismember (if you have R2012b+, I think) that will give you both of these indexes: _ismemberfirst (a builtin) and ismembc2.

For the example data A = [5 3 4 2]; B = [2 4 4 4 6 8]; in the question, here is the implementation:

[Bs,sortInds] = sort(B); % nop for this B, but required in general
firstInds = builtin('_ismemberfirst',A,Bs) % newish version required
firstInds =
     0     0     2     1
lastInds = ismembc2(A,Bs)
lastInds =
     0     0     4     1

The heavy lifting is now done - We have the first and last indexes in B for each element in A without having to do any looping. There is no occurrence of A(1) or A(2) (5 or 3) in B, so those indexes are 0. The value 4 (A(3)) occurs at locations 2:4 (i.e. all(B(2:4)==A(3))). Similarly, A(4) is found at B(1:1).

We are able to ignore sortInds in the above example since B is already sorted, but an unsorted B is handled by simply looking up the locations in the unsorted array. We can quickly do this lookup and package each range of indexes with arrayfun, keeping in mind that the computationally intensive task of actually finding the indexes is already done:

allInds = arrayfun(@(x,y)sortInds(x:y-(x==0)),firstInds,lastInds,'uni',0)
allInds = 
    [1x0 double]    [1x0 double]    [1x3 double]    [1]

Each cell has the indexes in B (if any) of each element of A. The first two cells are empty arrays, as expected. Looking closer at the third element:

>> allInds{3}
ans =
     2     3     4
>> A(3)
ans =
     4
>> B(allInds{3})
ans =
     4     4     4

Testing operation with unsorted B:

B(4:5) = B([5 4])
B =
     2     4     4     6     4     8

[Bs,sortInds] = sort(B);
firstInds = builtin('_ismemberfirst',A,Bs);
lastInds = ismembc2(A,Bs);
allInds = arrayfun(@(x,y)sortInds(x:y-(x==0)),firstInds,lastInds,'uni',0);

allInds{3} % of A(3) in B
ans =
     2     3     5

B(allInds{3})
ans =
     4     4     4

Is it worth doing it this way, with a penalty for sort and two effective ismember calls? Maybe not, but I think it's an interesting solution. If you have a sorted B, it's even faster since the two built-in functions assume the second argument (Bs) is sorted and waste no time with checks. Try and see what works for you.

Upvotes: 2

Luis Mendo
Luis Mendo

Reputation: 112679

A simple approach is to use bsxfun to test for equality between each element of A and B:

ind = bsxfun(@eq, A(:), B(:).');
list = cellfun(@find, mat2cell(ind, ones(1,numel(A)), numel(B)), 'uni', 0);

The matrix ind gives the result in logical form (i.e. 0 or 1 values), and list is a cell array containing the indices:

>>  ind

ind =

     0     0     0     0     0     0
     0     0     0     0     0     0
     0     1     1     1     0     0
     1     0     0     0     0     0

>> celldisp(list)

list{1} =

     []


list{2} =

     []    

list{3} =

     2     3     4

list{4} =

     1

Upvotes: 2

Robert Seifert
Robert Seifert

Reputation: 25232

The solutions of Eitan T. and Daniel R answer your question in total. My solution is a convenient and simpler alternative if you are just interested in the elements which are common in both vectors, but NOT how they are related in means of ismember, e.g. you just want to filter your data for common elements:

I would use the inversion of the opposite: setxor

A = [5 3 4 2]; 
B = [2 4 4 4 6 8];

[~,iai,ibi] = setxor(A,B);   % elements which are not in common
ia = 1:numel(A);
ib = 1:numel(B);
ia(iai) = [];                %indices of elements of B in A
ib(ibi) = [];                %indices of elements of A in B

Or just the same thing twice:

[~,iai,ibi] = setxor(A,B);
ia = setxor(1:numel(A),iai);
ib = setxor(1:numel(B),ibi);

returns in both cases the indices of the elements also existing in the respective other vector, so to say an implementation of ~isnotmember

ia =

     3     4

ib =

     1     2     3     4

Upvotes: 0

Eitan T
Eitan T

Reputation: 32930

You can swap the input arguments to ismember:

[tf, ia] = ismember(B, A)

For your example, you should get:

tf =
     1     1     1     1     0     0

ia = 
     4     3     3     3     0     0

This allows you to find, say, the indices of all the elements of B that equal A(3) simply by doing:

find(ia == 3)

Here's a nifty solution for the general case:

[tf, ia] = ismember(B, A);
idx = 1:numel(B);
ib = accumarray(nonzeros(ia), idx(tf), [], @(x){x});

Note that the output is a cell array. For your example, you should get:

ib = 
    []
    []
    [2     3     4]
    [      1]

which means that there are no elements in B matching A(1) and A(2), A(3) matches elements B(2), B(3) and B(4), and A(4) equals B(1).

Upvotes: 5

Daniel
Daniel

Reputation: 36710

This line will return all indices:

F=arrayfun(@(x)(find(A(x)==B)),1:numel(A),'UniformOutput',false)

Or a longer version, which might be easier to read:

F=cell(numel(A),1);
for x=1:numel(A)
  F{x}=find(A(x)==B);
end

find(A(x)==B) checks for all occurrences of A(x) in B. This is done for each element of the array, either using a for loop or arrayfun.

Upvotes: 4

Related Questions