【问题标题】:Programmatically get valid switch/case values以编程方式获取有效的开关/案例值
【发布时间】:2013-06-26 16:30:27
【问题描述】:

当 MATLAB 扫描 switch/case 块中的案例时,它是否记得它跳过的值,是否可以访问该列表?我有一些带有长 switch\case 块的函数,如果它们降低到 otherwise,我想让它们返回一个有效案例值的列表。例如,我有一个函数可以返回一组材料的光学常数。它目前有大约 20 种不同的材料,并且随着我考虑新的材料而不断增长。

我意识到我可以暴力破解它,只需将所有有效案例重新输入到otherwise 下的单元格数组中,并让函数抛出错误并返回有效响应列表,但保持两个列表没有错误或者随着时间的推移逐渐出现的懒惰是具有挑战性的。

【问题讨论】:

  • 你可能会觉得this很有趣:)
  • 非常好!我看到你最终选择了@mohsennosratina 的恐怖方法的一个版本:)。不过,有一条评论。如果我错了,请纠正我,但如果您要突出显示一段代码并按 F9 或使用 Shift+Enter 执行代码段(如果您在执行此操作之前未保存文件),则此代码将不起作用。
  • 确实如此;它必须从文件中运行。如果不要求你应该如何编写switch 的代码,我看不出怎么做。也许有一天我知道如何摆脱这种依赖:) 当我这样做时,我会让你知道。

标签: matlab


【解决方案1】:

为了澄清,听起来您要求做的事情类似于以下内容:

value = 'z';
output = [];
switch value
    case 'a'
        output = 1.234;
    case 'b'
        output = 2.345;
    case 'c'
        output = 3.456;
    otherwise
        output = [];
        disp('Please use one the the following values:  a, b, c')
        %It would be nice to auto-populate that string wouldn't it?
end

这在 Matlab(或我知道的任何语言)中是不可能直接实现的。


但是,如果您从 switch/case 语句转移到更加以数据为中心的代码设计,它就会变得容易。比如上面的代码可以改写为:

%Setup (this can be preloaded and stored as persistent if too time consuming)
count = 1;
allvalues(count).name = 'a'; allvalues(count).value = 1.234; count = count+1;
allvalues(count).name = 'b'; allvalues(count).value = 2.345; count = count+1;
allvalues(count).name = 'c'; allvalues(count).value = 3.456; count = count+1;

%Lookup
value = 'z';  %Also try value = 'a'

maskMatch = strcmp({allvalues.name},value);
if any(maskMatch)
    output = allvalues(maskMatch).value;
else
    disp('Please use one of the following values:');
    disp({allvalues.name});
end

这是使用结构数组存储数据的示例。有很多方法可以使用 Matlab 数据结构来存储这种数据,例如地图或元胞数组。如需较为全面的列表,请参阅此问题的答案:MATLAB Changing the name of a matrix with each iteration

【讨论】:

  • 这只有在特定情况下才有可能:如果一个case是一个字符串,下一个是function_handle,下一个是logical等等怎么办?
  • 然后我会将.value 字段替换为.functionHandle 字段。然后为每种情况编写一个函数(或匿名,如果它足够短)。代替output = data(mask).value;,使用fn=data(mask).functionHandle; output = fn();
  • 我说的是allvalues.name的内容。您正在使用 strcmp 针对所有支持的字符串 (allvalues.name) 对给定的字符串 (value) 进行矢量检查。这是一个特定的场景。如果value 的内容可以是任意类,而不是value = 'z';,该怎么办?这种方法将失去它曾经的优雅,尤其是与switch 相比。这种怪物的普遍性超过必须管理两个相同列表的负担......恕我直言。
  • 我误读了您的第一条评论。当然,正确的答案取决于问题的具体情况;我认为原始帖子是实现从材质名称到参数值的简单映射。如果 case 语句可以属于不同的类,那么您可以将 strcmp({allvalues.name},value) 替换为 cellfun(@(x)isequal(value), {allvalues.name})
  • 好吧,你可以投票给我 :) 这有点让人想起 Loren Shure 在how to set default values for function argument 上的博文中展示的技术。
【解决方案2】:

编辑:在我的第一个解决方案收到迷人的 cmets 后,我提出了另一个解决方案,它比第一个解决方案需要更多的代码编辑,但到目前为止仍比其他解决方案少(将原始解决方案移至结束):

让我们定义一个函数来获取值并将它们保存在一个持久变量中

function list = cc(value)
persistent allCases
if isempty(allCases) || (nargin == 0 && nargout == 0)
    allCases = {};
end
if nargin == 1,
    allCases = [allCases value]; 
    list = value;
end
if nargin == 0 && nargout == 1,
    list = allCases;
end
end

现在您只需在switch 之前添加cc; 即可重置持久变量并将case 语句中的所有值传递给函数,并在otherwise 部分调用函数以读取值:

a = 'a';
v = 'c';

cc;
switch a
    case cc({'b' v 1.2})
        %Multiple cases 
    case cc(2)
        %number
    case cc(ones(2))
        %matrix
    otherwise
        disp('Allowed cases are:');
        cellfun(@disp, cc);
end

打印出来:

Allowed cases are:
b
c
    1.2000
     2
     1     1
     1     1

有风险的解决方案:这个解决方案可能违反了很多编程实践,但仍然可以作为 hack。假设您没有嵌套了switch 语句,那么您可以在otherwise 语句中调用这样的函数:

function allCases = getCases
st = dbstack('-completenames');
line = st(2).line;
fLines = importdata(st(2).file, sprintf('\n'));
switchLine = find(~cellfun(@isempty, ...
    regexp(fLines(1:line-1), '^\s*switch\s', 'once')), 1, 'last');
otherwLine = find(~cellfun(@isempty, ...
    regexp(fLines(1:line-1), '^\s*otherwise\s*$', 'once')), 1, 'last');
caseLines = fLines(switchLine+1:otherwLine-1);
casesStr = regexprep(caseLines(~cellfun(@isempty, ...
    regexp(caseLines, '^\s*case\s', 'once'))), '^\s*case\s*', '');
casesCells = cell(size(casesStr));
for iCases = 1:numel(casesCells);
    casesCells{iCases} = evalin('caller', casesStr{iCases});
end
allCases = [casesCells{:}];
end

如果你运行这样的代码

a = 'a';
v = 'c';

switch a
    case {'b' v 1.2}
        %Multiple cases 
    case 2
        %number
    case ones(2)
        %matrix
    otherwise
        disp('Allowed cases are:');
        cellfun(@disp, getCases);
end

打印出来

Allowed cases are:
b
c
    1.2000
     2
     1     1
     1     1

【讨论】:

  • 这太可怕了。但实际上解决了所提出的无法解决的问题。我好矛盾。
  • 我明白了,您的函数是否将调用函数作为文本导入,搜索带有单词switchotherwise 的行,然后在这些行之间搜索单词@ 的所有实例987654335@,抓住并评估线路的其余部分,然后将其粘贴到单元格中?在我尝试其他选项之前,我还不知道我是否准备好走这条路,但 +1 表示狂妄自大。
  • +1:太可怕了!可怕!恶心!令人反感!可怕!完全没用,不可读,难以管理,......但是一个很棒的黑客:)
  • 你是个怪物:)。您的第二个解决方案非常优雅,并且完全符合我的要求。由于我不能单独投票,我将不得不接受整个事情作为我的答案。
  • 哎呀...对不起,我绝对打算给你一些荣誉。我将它包含在待处理的更新中:)
【解决方案3】:

AFAIK,没有这样的机制。在特定情况下可能会使用一些矢量化技巧,但一般说,不会。

而且,如果仅从(内存)效率的角度来看,实现这样的switch 也是一个坏主意(所有情况可能都是巨大的矩阵)。

你可以拼凑出这样一个机制:

% define all your cases in a cell
cases = {...
    'case1', 'case2', ...};

% and switch on these cases
switch [condition]
    case cases{1}
       % implement 'case1' 

   case cases{2}
       % implement 'case2' 

    ...

    otherwise
        char(cases) % contains all cases

end

显然,您在一般意义上获得的东西,在可读性方面损失;个别案例现在与其对应的代码位于同一位置。另外,cases 内容的顺序很重要,[继续列出许多缺点] ...

简而言之,它不是很漂亮。

您可以自行建立一个列表:

cases = {}; 

% FIRST CASE
if strcmp([condition], 'case1')
    % code for 'case1'

else % insert the case just checked for in the new list
    cases{end+1} = 'case1';
end

% SECOND CASE
if strcmp([condition], 'case2')
    % code for 'case2'

else % insert the case just checked for in the new list
    cases{end+1} = 'case2';
end

... % etc.

您实际上仍在构建 2 个列表:if-statements 中的一个“匿名”列表,cases 单元格数组中的一个。但是每个“案例”仍然是一个单独的实体,案例条件是按案例分组的。

当然,你确实失去了switch的力量。

...还有更多的方案。都具有可比性,并且都具有可比的缺点。

恐怕最好的办法就是忍受它并管理两个相同的列表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多