How to create equivalence class from an array in matlab - arrays

I am working on matlab and have an array:
a =
2 1 5 3 2 1 2 1
You can see there may be one value multiple times. I want a function that will give me an array for each of those value that contains the index(s) of that value in the array as output.
Using the above example, the output would be:
(1 5 7)
(2 6 8)
(3)
(4)
(1 5 7) are the indexes of 2 in the input array. Same happens for 1,5 and 3.
This can be done using for loops etc. I just want to know if there is some in-built function for this in matlab.
**** EDIT ****
There may be two columns as following.
2 1 5 3 2 1 2 1
3 4 3 2 4 4 3 4
In that case output will be
(1 7)
(2 6 8)
(3)
(4)
(5)

Use the third output of unique to get unique labels for each column of a, and then apply accumarray with a custom function:
[~, ~, kk] = unique(a.', 'rows', 'stable'); %'
result = accumarray(kk, (1:numel(kk)).', [], #(x) {sort(x).'});
This works for any number of rows. For your two-row example
a = [2 1 5 3 2 1 2 1
3 4 3 2 4 4 3 4];
the result is
result{1} =
1 7
result{2} =
2 6 8
result{3} =
3
result{4} =
4
result{5} =
5
If element order is not important, you can simplify the code a little:
[~, ~, kk] = unique(a.', 'rows'); %'
result = accumarray(kk, (1:numel(kk)).', [], #(x) {x.'});
which gives
result{1} =
2 8 6
result{2} =
7 1
result{3} =
5
result{4} =
4
result{5} =
3

I don't think there is a buit-in function to do that, but you can do it in one line with unique and arrayfun:
Res = arrayfun(#(x) find(x==a), unique (x, 'stable'), 'UniformOutput', false);
Best,

Extend Ratbert's nice answer to arrive at a more general one which addresses the "edited" request:
[~, ~, J]= unique(a.', 'rows');
Res = cellfun(#str2num,accumarray(J,[1:size(a,2)]',[], #(x)num2str(x),'uni',0);
Use of conversion from double to char and back is clunky, but it works with Octave 3.6.4, and should also work on MATLAB. In MATLAB a more elegant answer with accumarray is likely possible.
Edit: The following is a more elegant answer with accumarray (see the MATLAB documentation for more details) - equivalent to LuisMendo's answer.
[~, ~, J]= unique(a.', 'rows');
Res = accumarray(J,[1:numel(J)]',[],#(x){x});

Related

Rowwise 2 dimensional matrix intersection in Matlab

I will try to explain what I need through an example.
Suppose you have a matrix x as follows:
1 2 3
4 5 6
And another matrix y as follows:
1 4 5
7 4 8
What I need is (without looping over the rows) to perform an intersection between each 2 corresponding rows in x & y. So I wish to get a matrix z as follows:
1
4
The 1st rows in x and y only have 1 as the common value. The 2nd rows have 4 as the common value.
EDIT:
I forgot to add that in my case, it is guaranteed that the intersection results will have the same length and the length is always 1 actually.
I am thinking bsxfun -
y(squeeze(any(bsxfun(#eq,x,permute(y,[1 3 2])),2)))
Sample runs -
Run #1:
>> x
x =
1 2 3
4 5 6
>> y
y =
1 4 5
7 4 8
>> y(squeeze(any(bsxfun(#eq,x,permute(y,[1 3 2])),2)))
ans =
1
4
Run #2:
>> x
x =
3 5 7 9
2 7 9 0
>> y
y =
6 4 3
6 0 2
>> y(squeeze(any(bsxfun(#eq,x,permute(y,[1 3 2])),2)))
ans =
0
3
2
The idea is to put the matrices together and to look for duplicates in the rows. One idea to find duplicated numeric values is to diff them; the duplicates will be marked by the value 0 in result.
Which leads to:
%'Initial data'
A = [1 2 3; 8 5 6];
B = [1 4 5; 7 4 8];
%'Look in merged data'
V = sort([A,B],2); %'Sort matrix values in rows'
R = V(diff(V,1,2)==0); %'Find duplicates in rows'
This should work with any number of matrices that can be concatenated horizontally. It will detect all the duplicates, but it will return a column the same size as the number of rows only if there is one and only one duplicate per row in the matrices.

Removing any two array values whose difference is a member of that array

I am trying to remove array values whose difference is a member of that array in MATLAB. For example, if I have an array defined as
x = [1 2 4 3 7];
I would like to remove 2, because it can be achieved from 4 - 2. I would also like to remove 4 because it can be achieved from 7 - 3. I would then like to store these values (2 and 4, respectively) into a matrix. The latter is easy. I just have a hard time doing this checker for summation.
I know you can use
ismember(*any 2 differences*),x(:))
to check if the differences are in the array. However, I don't know how to code my function to try out all the combinations of element subtraction.
Seemed like a good setup to use bsxfun -
abs_diffs = abs(bsxfun(#minus,x(:),x(:).')) %//'
unq_abs_diffs = unique(abs_diffs)
out = x(~any(bsxfun(#eq,unq_abs_diffs(:),x(:).'),1)) %//'
%// OR x(~ismember(x,unq_abs_diffs))
Sample run -
>> x
x =
1 2 4 3 7
>> abs_diffs = abs(bsxfun(#minus,x(:),x(:).'))
abs_diffs =
0 1 3 2 6
1 0 2 1 5
3 2 0 1 3
2 1 1 0 4
6 5 3 4 0
>> unq_abs_diffs = unique(abs_diffs)
unq_abs_diffs =
0
1
2
3
4
5
6
>> out = x(~any(bsxfun(#eq,unq_abs_diffs(:),x(:).'),1))
out =
7
So, in [1 2 4 3 7], only 7 seemed like the one that could not be removed.
You could do it like this:
n = length(a);
differences = meshgrid(a,a) - meshgrid(a,a)'; % get differences between elements
differences(1:n+1:n*n) = []; % remove diagonal
a(ismember(a,differences)) = []; % remove elements in differences
I'm assuming that you only want differences between unique elements. If you want to allow the difference between an element of a and itself, then remove the 3rd line.

average operation in the first 2 of 3 dimensions of a matrix

Suppose A is a 3-D matrix as below (2 rows-2 columns-2 pages).
A(:,:,1)=[1,2;3,4];
A(:,:,2)=[5,6;7,8];
I want to have a vector, say "a", whose inputs are the average of diagonal elements of matrices on each page. So in this simple case, a=[(1+4)/2;(5+8)/2].
But I have difficulties in matlab to do so. I tried the codes below but failed.
mean(A(1,1,:),A(2,2,:))
You can use "partially linear indexing" in the two dimensions that define the diagonal, as follows:
Since partially linear indexing can only be applied on trailing dimensions, you first need to apply permute to rearrange dimensions, so that the first and second dimensions become second and third.
Now you leave the first dimension untouched, linearly-index the diagonals in the second and third dimensions (which effectly reduces those two dimensions to one), and apply mean along the (combined) second dimension.
Code:
B = permute(A, [3 1 2]); %// step 1: permute
result = mean(B(:,1:size(A,1)+1:size(A,1)*size(A,2)), 2); %// step 2: index and mean
In your example,
A(:,:,1)=[1,2;3,4];
A(:,:,2)=[5,6;7,8];
this gives
result =
2.5000
6.5000
You can use bsxfun for a generic solution -
[m,n,r] = size(A)
mean(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
Sample run -
>> A
A(:,:,1) =
8 4 1
7 6 3
1 5 8
A(:,:,2) =
1 7 6
8 5 2
1 2 7
A(:,:,3) =
6 2 8
1 1 6
1 4 5
A(:,:,4) =
8 1 6
1 5 1
9 2 7
>> [m,n,r] = size(A);
>> sum(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
ans =
22 13 12 20
>> mean(A(bsxfun(#plus,[1:n+1:n^2]',[0:r-1]*m*n)),1)
ans =
7.3333 4.3333 4 6.6667

merge two matrix and its attributes in matlab

I've two matrix a and b and I'd like to combine the rows in a way that in the first row I got no duplicate value and in the second value, columns in a & b which have the same row value get added together in new matrix. i.e.
a =
1 2 3
8 2 5
b =
1 2 5 7
2 4 6 1
Desired outputc =
1 2 3 5 7
10 6 5 6 1
Any help is welcomed,please.
For two-row matrices
You want to add second-row values corresponding to the same first-row value. This is a typical use of unique and accumarray:
[ii, ~, kk] = unique([a(1,:) b(1,:)]);
result = [ ii; accumarray(kk(:), [a(2,:) b(2,:)].').'];
General case
If you need to accumulate columns with an arbitrary number of columns (based on the first-row value), you can use sparse as follows:
[ii, ~, kk] = unique([a(1,:) b(1,:)]);
r = repmat((1:size(a,1)-1).', 1, numel(kk));
c = repmat(kk.', size(a,1)-1, 1);
result = [ii; full(sparse(r,c,[a(2:end,:) b(2:end,:)]))];

Non-repeated arrays in a matrix in matlab

I am looking for non-repeated rows in a matrix.
Assume:
A =
8 1
2 2
2 2
2 2
2 2
3 6
5 7
5 7
I would like to get "B" which is:
B=
8 1
3 6
Please mind C=unique(A,'rows') will give us unique rows of "A" which include repeated and non-repeated arrays and only remove repetitious rows. It means:
C =
2 2
3 6
5 7
8 1
"C" is not the one that I am looking for.
Any help would be greatly appreciated!
Use the second and third outputs of unique as follows:
[~, ii, jj] = unique(A,'rows');
kk = find(histc(jj,unique(jj))==1);
B = A(sort(ii(kk)),:);
Or use this more direct bsxfun-based approach:
B = A(sum(squeeze(all(bsxfun(#eq, A.', permute(A, [2 3 1])))))==1,:);
These two approaches work in quite generally: A may have any number of columns, and may contain non-integer values.
If A always has two columns and contains only integer values, you can also do it with accumarray, using the sparse option (sixth input argument) to save memory in case of large values:
[ii jj] = find(accumarray(A, 1, [], #sum, 0, true)==1);
B = [ii jj];
Or you can use sparse instead of accumarray:
[ii jj] = find(sparse(A(:,1),A(:,2),1)==1);
B = [ii jj];
If you don't care about the order of the rows, try this -
[C,~,ic] = unique(A,'rows','legacy')
B = C(histc(ic,unique(ic))==1,:)

Resources