Improve speed of appending array elements if not already in array - matlab

I have an array S, which has some number of unique elements. I want to append elements from array N which are not already in S.
A syntactically simple way to do this would be:
S = union( S, N, 'stable' );
I found manually appending could be quicker, using ismember or implicit expansion:
% ismember approach
S = [S; N(~ismember(N,S))];
% imp. expansion approach
S = [S; N(~any(S(:)==N(:).',1))];
However, this still feels pretty dirty to do inside a loop, and the implicit expansion can be expensive for large inputs.
Is there a more performant alternative?
If it helps, we can assume S and N contain only integers. However, we can't assume that S is sorted, new elements appended from N could be any positive integer.
Minimal example:
Ntest = [1 2 3 4
2 5 3 6
1 5 7 9];
S = [];
for ii = 1:3
N = Ntest(ii,:).';
S = union(S,N,'stable');
end
% S = [ 1; 2; 3; 4; 5; 6; 7; 9 ]
In the real case, I don't know the potential values of N up front like I did with Ntest above.
Here is some benchmarking code for 4 methods, with the following results. In my case, it's likely that I'll have a large loop for different values of N, and a small number of elements in each N. This corresponds to the right-most columns in this summary table, where you can see the implicit expansion method is much quicker.
range(Ntest): 1 to 1e4 1 to 1e4 1 to 1e4 1 to 1e4
size(Ntest): [1e3,1e3] [1e4,1e3] [1e2,1e3] [1e2,1e4]
union: 0.972 sec 1.217 sec 0.779 sec 9.341 sec
ismember: 0.763 sec 0.559 sec 0.492 sec 5.439 sec
implicit: 6.966 sec too long! 0.295 sec 3.886 sec
setdiff: 0.599 sec 0.534 sec 0.477 sec 5.364 sec
rng(0);
Ntest = randi([1,1e4],1e3,1e3);
f = #()f_union( Ntest );
fprintf( 'union: \t%.3f sec\n', timeit( f ) );
f = #()f_ismember( Ntest );
fprintf( 'ismember: \t%.3f sec\n', timeit( f ) );
f = #()f_implicit( Ntest );
fprintf( 'implicit: \t%.3f sec\n', timeit( f ) );
f = #()f_setdiff( Ntest );
fprintf( 'setdiff: \t%.3f sec\n', timeit( f ) );
function f_union( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = union(S,N,'stable');
end
end
function f_ismember( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = [S; N(~ismember(N,S))];
end
end
function f_implicit( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = [S; N(~any(S(:)==N(:).',1))];
end
end
function f_setdiff( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = [S;setdiff(N,S)];
end
end

Since it is assumed that the data type is positive integer you can use logical matrix to store position of integers:
function f_logical( Ntest )
S = false;
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S(N) = true;
end
end
If the range of elements is large and the data has sparsity it may be beneficial to use sparse matrix:
function f_sparse( Ntest )
S = sparse(false);
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S(N) = true;
end
end
Comparing with the ismember solution in Octave:
Elapsed time for <ismember> is 1.54181 seconds.
Elapsed time for <sparse> is 0.266474 seconds.
Elapsed time for <logical> is 0.0189412 seconds.

I guess you can use the following code to speed up
X = setdiff(N,S);
S(end + (1:length(X))) = X;
Remarks
X = N(~ismember(N,S)) and X = setdiff(N,S) are both fine to find the elements that in N but not in S, but the key step for speeding up the appending process is the following way
S(end + (1:length(X))) = X;
Performance Comparison
rng(0);
Ntest = randi([1,1e4],1e4,1e4);
f = #()f_union( Ntest );
fprintf( 'union: \t%.3f sec\n', timeit( f ) );
f = #()f_ismember_v1( Ntest );
fprintf( 'ismember_v1: \t%.3f sec\n', timeit( f ) );
f = #()f_ismember_v2( Ntest );
fprintf( 'ismember_v2: \t%.3f sec\n', timeit( f ) );
f = #()f_setdiff_v1( Ntest );
fprintf( 'setdiff_v1: \t%.3f sec\n', timeit( f ) );
f = #()f_setdiff_v2( Ntest );
fprintf( 'setdiff_v2: \t%.3f sec\n', timeit( f ) );
function f_union( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = union(S,N,'stable');
end
end
function f_ismember_v1( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = [S; N(~ismember(N,S))];
end
end
function f_ismember_v2( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
X = N(~ismember(N,S));
S(end + (1:length(X))) = X;
end
end
function f_setdiff_v1( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
S = [S;setdiff(N,S)];
end
end
function f_setdiff_v2( Ntest )
S = [];
for ii = 1:size(Ntest,2)
N = Ntest(:,ii);
X = setdiff(N,S);
S(end + (1:length(X))) = X;
end
end
giving
union: 13.314 sec
ismember_v1: 5.836 sec
ismember_v2: 5.658 sec
setdiff_v1: 4.371 sec
setdiff_v2: 4.248 sec

Related

Matlab Vectorization - none-zero matrix row indices to cell

I am working with Matlab.
I have a binary square matrix. For each row, there is one or more entries of 1. I want to go through each row of this matrix and return the index of those 1s and store them in the entry of a cell.
I was wondering if there is a way to do this without looping over all the rows of this matrix, as for loop is really slow in Matlab.
For example, my matrix
M = 0 1 0
1 0 1
1 1 1
Then eventually, I want something like
A = [2]
[1,3]
[1,2,3]
So A is a cell.
Is there a way to achieve this goal without using for loop, with the aim of calculating the result more quickly?
At the bottom of this answer is some benchmarking code, since you clarified that you're interested in performance rather than arbitrarily avoiding for loops.
In fact, I think for loops are probably the most performant option here. Since the "new" (2015b) JIT engine was introduced (source) for loops are not inherently slow - in fact they are optimised internally.
You can see from the benchmark that the mat2cell option offered by ThomasIsCoding here is very slow...
If we get rid of that line to make the scale clearer, then my splitapply method is fairly slow, obchardon's accumarray option is a bit better, but the fastest (and comparable) options are either using arrayfun (as also suggested by Thomas) or a for loop. Note that arrayfun is basically a for loop in disguise for most use-cases, so this isn't a surprising tie!
I would recommend you use a for loop for increased code readability and the best performance.
Edit:
If we assume that looping is the fastest approach, we can make some optimisations around the find command.
Specifically
Make M logical. As the below plot shows, this can be faster for relatively small M, but slower with the trade-off of type conversion for large M.
Use a logical M to index an array 1:size(M,2) instead of using find. This avoids the slowest part of the loop (the find command) and outweighs the type conversion overhead, making it the quickest option.
Here is my recommendation for best performance:
function A = f_forlooplogicalindexing( M )
M = logical(M);
k = 1:size(M,2);
N = size(M,1);
A = cell(N,1);
for r = 1:N
A{r} = k(M(r,:));
end
end
I've added this to the benchmark below, here is the comparison of loop-style approaches:
Benchmarking code:
rng(904); % Gives OP example for randi([0,1],3)
p = 2:12;
T = NaN( numel(p), 7 );
for ii = p
N = 2^ii;
M = randi([0,1],N);
fprintf( 'N = 2^%.0f = %.0f\n', log2(N), N );
f1 = #()f_arrayfun( M );
f2 = #()f_mat2cell( M );
f3 = #()f_accumarray( M );
f4 = #()f_splitapply( M );
f5 = #()f_forloop( M );
f6 = #()f_forlooplogical( M );
f7 = #()f_forlooplogicalindexing( M );
T(ii, 1) = timeit( f1 );
T(ii, 2) = timeit( f2 );
T(ii, 3) = timeit( f3 );
T(ii, 4) = timeit( f4 );
T(ii, 5) = timeit( f5 );
T(ii, 6) = timeit( f6 );
T(ii, 7) = timeit( f7 );
end
plot( (2.^p).', T(2:end,:) );
legend( {'arrayfun','mat2cell','accumarray','splitapply','for loop',...
'for loop logical', 'for loop logical + indexing'} );
grid on;
xlabel( 'N, where M = random N*N matrix of 1 or 0' );
ylabel( 'Execution time (s)' );
disp( 'Done' );
function A = f_arrayfun( M )
A = arrayfun(#(r) find(M(r,:)),1:size(M,1),'UniformOutput',false);
end
function A = f_mat2cell( M )
[i,j] = find(M.');
A = mat2cell(i,arrayfun(#(r) sum(j==r),min(j):max(j)));
end
function A = f_accumarray( M )
[val,ind] = ind2sub(size(M),find(M.'));
A = accumarray(ind,val,[],#(x) {x});
end
function A = f_splitapply( M )
[r,c] = find(M);
A = splitapply( #(x) {x}, c, r );
end
function A = f_forloop( M )
N = size(M,1);
A = cell(N,1);
for r = 1:N
A{r} = find(M(r,:));
end
end
function A = f_forlooplogical( M )
M = logical(M);
N = size(M,1);
A = cell(N,1);
for r = 1:N
A{r} = find(M(r,:));
end
end
function A = f_forlooplogicalindexing( M )
M = logical(M);
k = 1:size(M,2);
N = size(M,1);
A = cell(N,1);
for r = 1:N
A{r} = k(M(r,:));
end
end
You can try arrayfun like below, which sweep through rows of M
A = arrayfun(#(r) find(M(r,:)),1:size(M,1),'UniformOutput',false)
A =
{
[1,1] = 2
[1,2] =
1 3
[1,3] =
1 2 3
}
or (a slower approach by mat2cell)
[i,j] = find(M.');
A = mat2cell(i,arrayfun(#(r) sum(j==r),min(j):max(j)))
A =
{
[1,1] = 2
[2,1] =
1
3
[3,1] =
1
2
3
}
Using accumarray:
M = [0 1 0
1 0 1
1 1 1];
[val,ind] = find(M.');
A = accumarray(ind,val,[],#(x) {x});
You can use strfind :
A = strfind(cellstr(char(M)), char(1));
Edit: I added a benchmark, the results show that a for loop is more efficient than accumarray.
You can usefind and accumarray:
[c, r] = find(A');
C = accumarray(r, c, [], #(v) {v'});
The matrix is transposed (A') because find groups by column.
Example:
A = [1 0 0 1 0
0 1 0 0 0
0 0 1 1 0
1 0 1 0 1];
% Find nonzero rows and colums
[c, r] = find(A');
% Group row indices for each columns
C = accumarray(r, c, [], #(v) {v'});
% Display cell array contents
celldisp(C)
Output:
C{1} =
1 4
C{2} =
2
C{3} =
3 4
C{4} =
1 3 5
Benchmark:
m = 10000;
n = 10000;
A = randi([0 1], m,n);
disp('accumarray:')
tic
[c, r] = find(A');
C = accumarray(r, c, [], #(v) {v'});
toc
disp(' ')
disp('For loop:')
tic
C = cell([size(A,1) 1]);
for i = 1:size(A,1)
C{i} = find(A(i,:));
end
toc
Result:
accumarray:
Elapsed time is 2.407773 seconds.
For loop:
Elapsed time is 1.671387 seconds.
A for loop is more efficient than accumarray...

Compute sum of series

I need to compute the sum of this series
I need the output this way:
If n = 3;
x = function_name(n)
I need to get x = 11.
If n = 5;
x = function_name(n)
I need to get x = 45.
I believe I need a for-loop to iterate; but am finding it difficult to iterate the increment value itself.
I guess you want the sum of the cumsum of the differences d of the numbers:
d = 2;
n = 5;
s = d:d:d*(n-1)
cs = cumsum( [1 s] )
scs = sum(cs)
%// or as anonymous function
scsh = #(n,d) sum( cumsum( [1 d:d:d*(n-1)] ) )
scs =
45
scsh(5,2) =
45
No need for a loop!
inc=2;
sum=1;
next=1;
n=input('what is n?\n');
for i=2:n
next=next+inc;
sum=sum+next;
inc=inc+2;
end
disp('sum is ');
disp(sum);
function Total = abc(n)
nth_term=1;
Total = 1 ;
for d = 2:2:(2*(n-1))
nth_term = nth_term + d;
Total =Total + nth_term;
end
end

How can I easily create this matrix?

How can I easily create the following matrix in MATLAB?
With A (nxn), B (nxp) and N a positive integer.
Gamma = [B 0 0 ... 0 ;
A*B B 0 ... 0 ;
A^2*B A*B B ... 0 ;
... ... ... ... ... ;
A^(N-1)*B A^(N-2)*B A^(N-3)*B ... B];
How about
[g{1:N,1:N}] = deal( zeros(n,p) );
g{1,1} = B;
for ii = 2:N
g( ii, 2:end ) = g( ii-1, 1:end-1 );
g{ ii, 1 } = A * g{ ii-1, 1 };
end
Gamma = cell2mat( g );
This works, though it is less efficient than it could be.
n = size(A,1);
p = size(B,2);
N = 3;
Gamma = zeros(N*n, N*p);
for ii = 1:N
for jj = 1:N
if ii >= jj
Gamma((ii-1)*n+1:ii*n,(jj-1)*p+1:jj*p) = A^(ii-jj) * B;
end
end
end
Edit: Here's a more efficient version, that does the minimum amount of matrix multiplication.
n = size(A,1);
p = size(A,2);
N = 3;
Gamma = zeros(N*n, N*p);
# Pre-compute all the matrix multiplications we'll need.
memo = cell(1, N);
memo{1} = B;
for ii = 1:N-1
memo{ii+1} = A * memo{ii};
end
for ii = 1:N
for jj = 1:N
if ii >= jj
Gamma((ii-1)*n+1:ii*n,(jj-1)*p+1:jj*p) = memo{ii-jj+1};
end
end
end

Matlab matrix operations without loops

I have an issue with a code performing some array operations. It is getting too slow, because I am using loops. I am trying for some time to optimize this code and to re-write it with less or without loops. Until now unsuccessfully. Can you please help me solve this:
YVal = 1:1:100000;
M_MAX = 1000;
N_MAX = 2000;
clear YTemp
tic
for M=1:1:M_MAX
for N = 1:1:N_MAX
YTemp(M,N) = sum(YVal (N+1:N+M) ) - sum(YVal (1:M) );
end
end
For large N_MAX and M_MAX the execution time of these two loops is very high. How can I optimize this?
Thank you!
Assuming YVal is larger than N_MAX+M_MAX
sum1 = cumsum( YVal(1:(M_MAX+N_MAX)) ); % sum1(M) = sum(YVal(1:M))
If I'm not mistaken, then
sum( YVal( N+1:N+M ) ) = sum1( N + M ) - sum1( N )
And therefore
YTemp( M, N ) = sum1( N + M ) - sum1( N ) - sum1( M )
Using ndgrid
[M N] = ndgrid( 1:M_MAX, 1:N_MAX );
YTemp = sum1( N + M ) - sum1( N ) - sum1( M );
Have I got it right?
EDIT:
Another go without ndgrid
sum1 = cumsum( YVal( 1 : (N_MAX+M_MAX) ) );
YTemp = bsxfun( #minus, ...
bsxfun( #minus, ...
sum1( bsxfun( #plus, 1:N_MAX, (1:M_MAX)' ) ) , sum1 ),...
sum1' );
You should be able to speed it up a little by hoisting the invariant term out of the inner loop, e.g.
for M=1:1:M_MAX
sum2 = sum(YVal(1:M));
for N = 1:1:N_MAX
YTemp(M,N) = sum(YVal(N+1:N+M)) - sum2;
end
end

Intersection indices by rows

Given these two matrices:
m1 = [ 1 1;
2 2;
3 3;
4 4;
5 5 ];
m2 = [ 4 2;
1 1;
4 4;
7 5 ];
I'm looking for a function, such as:
indices = GetIntersectionIndecies (m1,m2);
That the output of which will be
indices =
1
0
0
1
0
How can I find the intersection indices of rows between these two matrices without using a loop ?
One possible solution:
function [Index] = GetIntersectionIndicies(m1, m2)
[~, I1] = intersect(m1, m2, 'rows');
Index = zeros(size(m1, 1), 1);
Index(I1) = 1;
By the way, I love the inventive solution of #Shai, and it is much faster than my solution if your matrices are small. But if your matrices are large, then my solution will dominate. This is because if we set T = size(m1, 1), then the tmp variable in the answer of #Shai will be T*T, ie a very large matrix if T is large. Here's some code for a quick speed test:
%# Set parameters
T = 1000;
M = 10;
%# Build test matrices
m1 = randi(5, T, 2);
m2 = randi(5, T, 2);
%# My solution
tic
for m = 1:M
[~, I1] = intersect(m1, m2, 'rows');
Index = zeros(size(m1, 1), 1);
Index(I1) = 1;
end
toc
%# #Shai solution
tic
for m = 1:M
tmp = bsxfun( #eq, permute( m1, [ 1 3 2 ] ), permute( m2, [ 3 1 2 ] ) );
tmp = all( tmp, 3 ); % tmp(i,j) is true iff m1(i,:) == m2(j,:)
imdices = any( tmp, 2 );
end
toc
Set T = 10 and M = 1000, and we get:
Elapsed time is 0.404726 seconds. %# My solution
Elapsed time is 0.017669 seconds. %# #Shai solution
But set T = 1000 and M = 100 and we get:
Elapsed time is 0.068831 seconds. %# My solution
Elapsed time is 0.508370 seconds. %# #Shai solution
How about using bsxfun
function indices = GetIntersectionIndecies( m1, m2 )
tmp = bsxfun( #eq, permute( m1, [ 1 3 2 ] ), permute( m2, [ 3 1 2 ] ) );
tmp = all( tmp, 3 ); % tmp(i,j) is true iff m1(i,:) == m2(j,:)
indices = any( tmp, 2 );
end
Cheers!