如何将任意数据类型写入Matlab单元格数组

Car*_*oft 2 matlab types function return-value cell-array

这是一个普遍问题,与特定操作无关.我希望能够将任意函数的结果写入单元格数组的元素,而不考虑函数返回的数据类型.考虑这个伪代码:

zout = cell(n,m);
myfunc = str2func('inputname'); %assume myfunc puts out m values to match zout dimensions
zout(1,:) = myfunc(x,y);
Run Code Online (Sandbox Code Playgroud)

这将适用于"inputname"=="strcat",例如,给定它x并且y是具有适当维度的字符串或单元格.但是如果"inputname"=="strcmp"则输出是逻辑数组,并且Matlab会抛出错误.我需要这样做

zout(1,:) = num2cell(strcmp(x,y));
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:有没有办法填充单元格数zout而不必测试生成的变量类型myfunc(x,y ?我应该首先使用a struct(如果是这样,最好的方法是填充它)?
(我通常是R用户,我可以在list没有任何痛苦的情况下使用变量)

编辑:为了简化整体范围,添加以下"要求":我们现在假设,对于返回多个输出的函数,只需要捕获第一个zout.但是当此输出是N值的向量或单元的向量(即Nx1单元阵列)时,这些N值将被映射到zout(1,1:N).

sfs*_*man 5

所以我的问题是:有没有办法填充单元格数组zout而不必测试myfunc(x,y)生成的变量类型?我应该首先使用结构(如果是,那么填充它的最佳方法是什么)?

@NotBoStyf提供的答案几乎就在那里,但并不完全.细胞阵列是正确的方法.但是,答案很大程度上取决于函数的输出数量.

只有一个输出的功能

该函数strcmp只有一个输出,即一个数组.原因

zout{1,:} = strcmp(x,y)
Run Code Online (Sandbox Code Playgroud)

当zout的尺寸为N x 2时,会给出一条错误消息,即左侧(zout{1,:})需要右侧的两个输出.你可以解决这个问题:

[zout{1,:}] = num2cell(strcmp(x,y));  % notice the square brackets on the LHS
Run Code Online (Sandbox Code Playgroud)

但是,没有理由这样做.您可以简单地定义zout为N x 1单元阵列并捕获结果:

zout = cell(1,1);

x = 'a';
y = { 'a', 'b' };

zout{1} = strcmp(x,y);

% Referring to the results:
x_is_y_1 = zout{1}(1);
x_is_y_2 = zout{1}(2);
Run Code Online (Sandbox Code Playgroud)

还有一个案例需要考虑......

具有多个输出的功能

如果您的函数产生多个输出(而不是作为数组的单个输出),那么这将仅捕获第一个输出.产生多个输出的函数定义如下:

function [outA,outB] = do_something( a, b )
  outA = a + 1;
  outB = b + 2;
end
Run Code Online (Sandbox Code Playgroud)

在这里,您需要显式捕获两个输出参数.否则,你就得到了a.例如:

outA = do_something( [1,2,3], [4,5,6] ); % outA is [2,3,4]

[outA,outB] = do_something( [1,2,3], [4,5,6] ); % outA is [2,3,4], outB is [6,7,8]

Z1 = cell(1,1);
Z1{1,1} = do_something( [1,2,3], [4,5,6] ); % Z1{1,1} is [2,3,4]

Z2 = cell(1,2);
Z2{1,1:2} = do_something( [1,2,3], [4,5,6] ); % Same error as above.  
% NB: You really never want to have a cell expansion that is not surrounded 
% by square brackets.

% Do this instead:
[Z2{1,1:2}] = do_something( [1,2,3], [4,5,6] ); % Z2{1,1} is [2,3,4], Z2{1,2} is [6,7,8]
Run Code Online (Sandbox Code Playgroud)

这也可以通过编程方式完成,但有一些限制.假设我们给出func了一个函数 ,它接受一个输入并返回一个常量(但未知)的输出数.我们有inp包含我们想要处理的输入的单元格数组,我们希望在单元格中收集结果outp:

N = numel(inp);
M = nargout(@func);  % number of outputs produced by func 
outp = cell(N,M);
for i=1:N
  [ outp{i,:} ] = func( inp{i} );
end
Run Code Online (Sandbox Code Playgroud)

这种方法有一些注意事项:

  1. 它捕获所有输出.这并不总是你想要的.

  2. 捕获所有输出通常可以改变函数的行为.例如,find如果仅使用一个输出,则函数返回线性索引;如果使用两个输出,则返回行/列索引;如果使用三个输出,则返回行/列/值.

  3. 它不适用于具有可变输出数量的函数.这些功能定义为function [a,b,...,varargout] = func( ... ). nargout如果函数已varargout在其输出列表中声明,则将返回负数,因为Matlab无法知道将产生多少输出.

将数组和单元输出解压缩到单元格中

到目前为止一切都是真的,但是:我所希望的是一个通用的解决方案.如果函数产生单元格输出,我不能使用num2cell.那么strcmp的工作原理对于strcat来说会失败,反之亦然.我们现在假设,对于一个返回多个输出的函数,只需要在zout中捕获第一个 - Carl Witthoft

要为返回单元格或数组的所有函数提供统一的输出语法,请使用适配器函数.这是一个处理数字数组和单元格的示例:

function [cellOut] = cellify(input)
  if iscell(input)
    cellOut = input;
  elseif isnumeric(input)
    cellOut = num2cell(input);
  else
    error('cellify currently does not support structs or objects');
  end
end
Run Code Online (Sandbox Code Playgroud)

要将输出解压缩为2-D单元阵列,每个输出的大小必须是常量.假设M输出:

N = numel(inp);
% M is known and constant
outp = cell(N,M);
for i=1:N
  outp(i,:) = cellify( func( inp{i} ) );  % NB: parentheses instead of curlies on LHS
end
Run Code Online (Sandbox Code Playgroud)

然后输出可以作为outp{i,j}.另一种方法允许输出的大小变化:

N = numel(inp);
% M is not necessary here
outp = cell(N,1);
for i=1:N
  outp{i} = cellify( func( inp{i} ) );  % NB: back to curlies on LHS
end
Run Code Online (Sandbox Code Playgroud)

然后可以将输出作为outp{i}{j},并且输出的大小可以变化.

要记住以下几点:

  1. Matlab单元基本上是效率低下的指针.JIT编译器并不总是优化它们以及数值数组.

  2. 将数字数组拆分成单元可能会花费相当多的内存.每个拆分值实际上是一个数字数组,其中包含与之关联的大小和类型信息.在数字数组形式中,每个数组都会出现一次.当数组被拆分时,每个元素都会产生一次.