【问题标题】:How to write arbitrary datatypes into Matlab cell array如何将任意数据类型写入 Matlab 单元数组
【发布时间】:2012-06-26 14:06:11
【问题描述】:

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

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

这适用于 "inputname" == "strcat" ,例如,假设 xy 是具有适当维度的字符串或字符串单元格。但是如果 "inputname" == "strcmp" 那么输出是一个逻辑数组,并且 Matlab 会抛出一个错误。我需要这样做

zout(1,:) = num2cell(strcmp(x,y));

所以我的问题是:有没有一种方法可以填充单元格数组 zout 而无需测试 myfunc(x,y 生成的变量类型?我应该首先使用struct(如果是,填充它的最佳方法是什么)?
(我通常是 R 用户,我可以使用 list 变量而没有任何痛苦)

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

【问题讨论】:

  • 输出参数的数量如何,您要使用的所有函数是否都返回 1 个变量?他们使用varargout吗?
  • @Amro 我想让它尽可能通用,以便函数返回的任何内容(数字、几个数字、字符串、逻辑值)都放在指定的单元格中。示例:x = 1:4y = 6myfunc = atan2,所以zout(1,1:4)(四个单元格)填充了从atan2 返回的四个值。
  • @CarlWitthot:我说的是返回的变量数量而不是它们的类型。我问的原因是某些函数只会返回您请求的输出参数。以unique 为例,它最多返回3 个变量,并且无法通过编程方式知道函数的输出数量(定义为function varargout = unique(varargin))。如果您调用nargout('unique'),它会显示-1(减去表示可变参数,因此只报告一个参数)
  • @CarlWitthot:只是指出这一点,您的 ATAN2 示例返回一个输出参数而不是四个,尽管它是一个长度为 4 的向量。这就是您正在寻找的行为,每个 数字单独在一个单元格中?
  • @Amro 是的,如果输出是一个向量,那么向量的每个元素都会进入它自己的单元格。如果输出是 Mx1 元胞数组,则每个元胞都成为 zout 中的匹配元胞(不是元胞内的元胞)。我正在寻找 DWIM 行为。 zout 是一个元胞数组的原因是,无论值的类型(数字、逻辑、字符)如何,我都可以将每个单独的值放入其自己的“位置”。

标签: function matlab types return-value cell-array


【解决方案1】:

所以我的问题是:有没有一种方法可以填充元胞数组 zout 而无需测试 myfunc(x,y) 生成的变量类型?我应该首先使用结构吗(如果是,填充它的最佳方法是什么)?

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

只有一个输出的函数

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

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

当 zout 的尺寸为 N x 2 时,会给出一条错误消息,即左侧 (zout{1,:}) 需要来自右侧的两个输出。您可以通过以下方式解决此问题:

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

但是,确实没有理由这样做。您可以简单地将 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);

还有一种情况需要考虑……

具有多个输出的函数

如果您的函数产生多个输出(而不是一个数组的单个输出),那么这只会捕获第一个输出。产生多个输出的函数定义如下:

function [outA,outB] = do_something( a, b )
  outA = a + 1;
  outB = b + 2;
end

在这里,您需要显式捕获两个输出参数。否则,你只会得到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]

这也可以通过编程方式完成,但有一些限制。假设我们被赋予了功能 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

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

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

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

  3. 它不适用于具有可变数量输出的函数。这些函数定义为function [a,b,...,varargout] = func( ... )。如果函数在其输出列表中声明了varargoutnargout 将返回一个负数,因为 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

要将输出解包为二维元胞数组,每个输出的大小必须是恒定的。假设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

然后可以将输出寻址为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

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

需要注意的几点:

  1. Matlab 单元基本上是低效的指针。 JIT 编译器并不总是像数值数组一样优化它们。

  2. 将数值数组拆分为单元格会占用大量内存。每个拆分值实际上是一个数值数组,具有与其相关的大小和类型信息。在数值数组形式中,每个数组都会发生一次。当数组被拆分时,每个元素都会发生一次。

【讨论】:

  • 到目前为止一切都是正确的,但是:我希望的是一个通用的解决方案。如果函数产生单元格输出,我不能使用num2cell。因此,对strcmp 有效的方法对strcat 将失败,反之亦然。现在让我们假设,对于一个返回多个输出的函数,只有第一个需要在zout 中捕获
  • +1 解释得很好——我正在写类似的东西,但你打败了我:)
  • @CarlWitthoft 如果只需要捕获第一个,则将其视为单输出函数。我将使用 Nx1 单元阵列并直接捕获单元阵列中的输出:zout{i} = func(...)。您只需要使用 NxM 元胞数组来捕获多个输出。与使用zout{i}(j) 索引函数的输出相比,使用num2cell 确实没有任何好处。如果函数输出一个单元格,那么num2cell 将不起作用(单元格不是数字),您最好使用zout{i}{j}。如果函数输出结构体也是如此:zout{i}.field
  • @CarlWitthoft 不要挂断num2cell。你不需要它。 x{1} = func(y) 会将函数的结果分配到一个单元格中,无论它是一个数字、一个字符串、一个单元格、一个单元格数组,等等。一个细胞可以容纳任何东西。如果您尝试使用() 而不是{} 索引,则只需使用num2cell 之类的东西。
  • @CarlWitthot:如果您的目标是无论如何简单地“捕获输出”(假设一个输出变量),只需使用常规单元格数组。这是一个示例:x = {'a','b'}; y = 'a'; fcn = {'strcmp','strcat'}; z = cellfun(@(f) feval(f,x,y), fcn, 'Uniform',false);。您可以通过以下方式访问输出:z{1}, z{2}{1}, z{2}{2}。如果您愿意,您现在可以将结果“展平”作为第二步。
【解决方案2】:

在赋值时使用花括号代替。 使用

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

应该可以。

【讨论】:

  • 您不再需要带有花括号索引的num2cell
  • 另外:如果要分配到元胞数组中的单个位置,则需要 {1,1} 而不是 {1,:}
  • 这不起作用。 x = num2cell('a'); y = num2cell('ab'); strcmp(x,y) 返回 `1 0`。但是:对于尺寸为 N,2 的单元格数组 zoutzout{1,:} = strcmp(x,y) 会给出错误消息“??? 此赋值的右侧的值太少,无法满足左侧的要求。”我一直在尝试,但我还没有看到任何方法可以使用 zout{1,:} = 将项目分配给 zout 中的单独单元格。充其量我得到嵌套的单元格数组,这不是我想要的。大括号甚至会导致 strcat 案例失败。
  • @CarlWitthoft 这是因为 Matlab 根据您的索引方式以及花括号和弯括号的使用来确定大小的方式。只需输入size(zout{1,:}) 并查看结果。将其与size(strcmp(x,y)) 进行比较,您会发现它们确实不同。您需要根据预期的回报来设计您的代码,以便能够像这样将其放入元胞数组中。
猜你喜欢
  • 2014-01-16
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 2012-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-24
相关资源
最近更新 更多