Can I prevent Matlab from dynamically resizing a pre-allocated array?

匿名 (未验证) 提交于 2019-12-03 02:56:01

问题:

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 ...



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!