可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
For example, in this simple/stupid example:
n = 3; x = zeros(n, 1); for ix=1:4 x(ix) = ix; end
the array is pre-allocated, 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 pre-allocated my matrices properly.
回答1:
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
回答2:
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
回答3:
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.
回答4:
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 ...