multiple CASE statements on interval within nested

2019-02-19 13:44发布

问题:

Although I have come up with a work-around using multiple if / else if statements, I am curious in knowing what looks wrong with my case statements, illustrated below:

function [ar_vo,bucket] = revEng(v)
...
s=solve(solve>0) * sqrt(T); 
ar_vo=s; 
bucket=ri(ar_vo);
%%%%%%%%%%%%%%%%%%%%%
function bucket = ri(ar_vo)
%          switch(ar_vo)
%              case ((ar_vo >= 0)&&(ar_vo < 0.005)) (1)
%                   bucket=1;
%              case ((ar_vo >= 0.005)&&(ar_vo < 0.02)) (2)
%                   bucket=2;
%              case ((ar_vo >= 0.02)&&(ar_vo < 0.05)) (3)
%                   bucket=3;
%              case ((ar_vo >= 0.05)&&(ar_vo < 0.1)) (4)
%                   bucket=4;
%              case ((ar_vo >= 0.1)&&(ar_vo < 0.15)) (5)
%                   bucket=5;
%              case ((ar_vol >= 0.15)&&(ar_vol < 0.25)) (6)
%                   bucket=6;
%              case (ar_vo >= 0.25)                     (7)
%                   bucket=7;
%              otherwise
%                   error('MATLAB:RI Bucket:NotAvailable.'); (8)
%          end
%      

Given ar_vo = 0.073... while debugging the code, it skips case (4) for some reasons and reaches case (8)...

Inefficient but working solution with multiple if / else if:

    if ((ar_vo >= 0)&&(ar_vo < 0.005))
            bucket=1;
          else if ((ar_vo >= 0.005)&&(ar_vo < 0.02))
                 bucket=2;
              else if ((ar_vo >= 0.02)&&(ar_vo < 0.05))
                     bucket=3;
                  else if ((ar_vo >= 0.05)&&(ar_vo < 0.1))
                        bucket=4;
                      else if ((ar_vo >= 0.1)&&(ar_vo < 0.15))
                           bucket=5;
                          else if ((ar_vo >= 0.15)&&(ar_vo < 0.25))
                               bucket=6;
                              else if (ar_vo >= 0.25)
                                 bucket=7;
                                  else 
                                      error('MATLAB:RI Bucket:NotAvailable.');
                                  end
                              end
                          end
                      end
                  end
              end
          end

回答1:

The case expressions for your switch statement cannot be conditionals in MATLAB. They must be values.

switch thing
    case 1
        % do thing
    case {2, 3}
        % do other thing
    otherwise
end

So what MATLAB is doing in your case, is that it is converting your conditionals into values. So when you supply a value of 0.073 what MATLAB sees is this.

switch 0.73
    case 0
    case 0
    case 0
    case 1
    case 0
    case 0
    case 0
    otherwise
end

Since 0.73 obviously doesn't match any of those values you fall through to the otherwise and receive the error.

This is why switch statements are really only best for categorical data where you are comparing the input value against exact possible values (obviously not good for a floating point number).

A crazy solution

If you REALLY want to keep this as a switch statement you could do a little trickery and actually make your switch expression simply "1" (true) and it will behave as you want it to.

switch(1)
    case ((ar_vo >= 0)&&(ar_vo < 0.005))
        bucket=1;
    case ((ar_vo >= 0.005)&&(ar_vo < 0.02))
        bucket=2;
    case ((ar_vo >= 0.02)&&(ar_vo < 0.05))
        bucket=3;
    case ((ar_vo >= 0.05)&&(ar_vo < 0.1))
        bucket=4;
    case ((ar_vo >= 0.1)&&(ar_vo < 0.15))
        bucket=5;
    case ((ar_vo >= 0.15)&&(ar_vo < 0.25))
        bucket=6;
    case (ar_vo >= 0.25)
        bucket=7;
    otherwise
        error('MATLAB:RI Bucket:NotAvailable.');
end

Please don't actually do this.

A Real Solution

One sane approach is to use a series of if/elseif statements (as opposed to your long tree of if else if statements). This is a good approach (and is well-suited to floating point numbers) as it simply checks if that number is within a given range.

if ((ar_vo >= 0)&&(ar_vo < 0.005))
    bucket=1;
elseif ((ar_vo >= 0.005)&&(ar_vo < 0.02))
    bucket=2;
elseif ((ar_vo >= 0.02)&&(ar_vo < 0.05))
    bucket=3;
elseif ((ar_vo >= 0.05)&&(ar_vo < 0.1))
    bucket=4;
elseif ((ar_vo >= 0.1)&&(ar_vo < 0.15))
    bucket=5;
elseif ((ar_vo >= 0.15)&&(ar_vo < 0.25))
    bucket=6;
elseif (ar_vo >= 0.25)
    bucket=7;
else 
    error('MATLAB:RI Bucket:NotAvailable.');
end

The Best Solution

What I personally would do though, would be to remove all of that code and simply replace it with the following statements.

lowerlimits = [0, 0.005, 0.02, 0.05, 0.1, 0.15, 0.25]
upperlimits = [lowerlimits(2:end), inf];

bucket = find(ar_vo >= lowerlimits & ar_vo < upperlimits);

if isempty(bucket)
    error('MATLAB:RI Bucket:NotAvailable.');
end

In this approach I compare ar_vo to all ranges simultaneously and then get the bucket value by using the index of the match. If there was no bucket assigned, there was no match, and bucket is an empty array.

This drastically reduces the chance of copy/paste errors and makes it easier if you want to modify the conditions at a later date. It is likely also more performant particularly for values > 0.25 which would have to traverse your entire if/elseif construct.



回答2:

Building upon Suever's answer, and considering that you're actually trying to solve a data-binning problem, if you have R2015a or later, you can also use the built-in discretize function to achieve the same thing; e.g.:

function bucket = ri(ar_vo,edgz)
  if nargin < 1
    edgz = [0, 0.005, 0.02, 0.05, 0.1, 0.15, 0.25, inf];
  end
  bucket = discretize(ar_vo,edgz);
  if isnan(bucket)
      error('MATLAB:RI Bucket:NotAvailable.');
  end
end