Reputation: 4613
For example, in this simple/stupid example:
n = 3;
x = zeros(n, 1);
for ix=1:4
x(ix) = ix;
end
the array is preallocated, but dynamically resized in the loop. Is there a setting in MATLAB that will throw an error when dynamic resizing like this occurs? In this example I could trivially rewrite it:
n = 3;
x = zeros(n, 1);
for ix=1:4
if ix > n
error('Size:Dynamic', 'Dynamic resizing will occur.')
end
x(ix) = ix;
end
But I'm hoping to use this as a check to make sure I've preallocated my matrices properly.
Upvotes: 9
Views: 1189
Reputation: 18488
Allowing assignment to indices outside of an array's bounds and filling the gaps with zeros is indeed one of MATLAB's ugly parts. I am not aware of any simple tricks without an explicit check to avoid that, other than implementing your own storage class. I would stick to adding a simple assert(i <= n)
to your loop and forget about it. I have never been bitten by hard-to-find bugs due to assigning something out of bounds.
In case of a forgotten or too small preallocation, in the 'ideal' case your code gets really slow due to quadratic behavior, after which you find the bug and fix it. But these days, MATLAB's JIT is sometimes smart enough to not cause any slowdowns (maybe it dynamically grows arrays in some cases, like Python's list), so it might not even be an issue anymore. So it actually allows for some sloppier coding...
Upvotes: 2
Reputation: 9864
You can create a subclass of double
and restrict the assignment in subsasgn
method:
classdef dbl < double
methods
function obj = dbl(d)
obj = obj@double(d);
end
function obj = subsasgn(obj,s,val)
if strcmp(s.type, '()')
mx = cellfun(@max, s.subs).*~strcmp(s.subs, ':');
sz = size(obj);
nx = numel(mx);
if nx < numel(sz)
sz = [sz(1:nx-1) prod(sz(nx:end))];
end
assert(all( mx <= sz), ...
'Index exceeds matrix dimensions.');
end
obj = subsasgn@double(obj, s, val);
end
end
end
So now when you are preallocating use dbl
>> z = dbl(zeros(3))
z =
dbl
double data:
0 0 0
0 0 0
0 0 0
Methods, Superclasses
All methods for double
are now inherited by dbl
and you can use it as usual until you assign something to z
>> z(1:2,2:3) = 6
z =
dbl
double data:
0 6 6
0 6 6
0 0 0
Methods, Superclasses
>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.
I haven't benchmarked it but I expect this to have insignificant performance impact.
If you want the display of the values look normal you can overload the display
method as well:
function display(obj)
display(double(obj));
end
Then
>> z = dbl(zeros(3))
ans =
0 0 0
0 0 0
0 0 0
>> z(1:2,2:3) = 6
ans =
0 6 6
0 6 6
0 0 0
>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.
>> class(z)
ans =
dbl
Upvotes: 9
Reputation: 7423
This is not a fully worked example (see disclaimer after the code!) but it shows one idea...
You could (at least while debugging your code), use the following class in place of zeros to allocate your original variable.
Subsequent use of the data outside of the bounds of the originally allocated size would result in an 'Index exceeds matrix dimensions.' error.
For example:
>> n = 3;
>> x = zeros_debug(n, 1)
x =
0
0
0
>> x(2) = 32
x =
0
32
0
>> x(5) = 3
Error using zeros_debug/subsasgn (line 42)
Index exceeds matrix dimensions.
>>
The class code:
classdef zeros_debug < handle
properties (Hidden)
Data
end
methods
function obj = zeros_debug(M,N)
if nargin < 2
N = M;
end
obj.Data = zeros(M,N);
end
function sref = subsref(obj,s)
switch s(1).type
case '()'
if length(s)<2
% Note that obj.Data is passed to subsref
sref = builtin('subsref',obj.Data,s);
return
else
sref = builtin('subsref',obj,s);
end
otherwise,
error('zeros_debug:subsref',...
'Not a supported subscripted reference')
end
end
function obj = subsasgn(obj,s,val)
if isempty(s) && strcmp(class(val),'zeros_debug')
obj = zeros_debug(val.Data);
end
switch s(1).type
case '.'
obj = builtin('subsasgn',obj,s,val);
case '()'
if strcmp(class(val),'double')
switch length(s(1).subs{1})
case 1,
if s(1).subs{1} > length(obj.Data)
error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
end
case 2,
if s(1).subs{1} > size(obj.Data,1) || ...
s(1).subs{2} > size(obj.Data,2)
error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
end
end
snew = substruct('.','Data','()',s(1).subs(:));
obj = subsasgn(obj,snew,val);
end
otherwise,
error('zeros_debug:subsasgn',...
'Not a supported subscripted assignment')
end
end
function disp( obj )
disp(obj.Data);
end
end
end
There would be considerable performance implications (and problems stemming from using a class inheriting from handle) but it seemed like an interesting solution to the original problem.
Upvotes: 3
Reputation: 31352
The simplest, most straightforward and robust way I can think of to do this is just by accessing the index before assigning to it. Unfortunately, you cannot overload subsasgn for fundamental types (and it'd be a major headache to do correctly in any case).
for ix=1:4
x(ix); x(ix) = ix;
end
% Error: 'Attempted to access x(4); index out of bounds because numel(x)=3.'
Alternatively, you could try to be clever and do something with the end
keyword... but no matter what you do you'll end up with some sort of nonsensical error message (which the above nicely provides).
for ix=1:4
x(ix*(ix<=end)) = ix;
end
% Error: 'Attempted to access x(0); index must be a positive integer or logical.'
Or you could do that check in a function, which gains you your nice error message but is still terribly verbose and obfuscated:
for ix=1:4
x(idxchk(ix,end)) = ix;
end
function idx = idxchk(idx,e)
assert(idx <= e, 'Size:Dynamic', 'Dynamic resizing will occur.')
end
Upvotes: 5