如何在不先将 MATLAB 函数返回的数组分配给本地变量的情况下对其进行索引?

399
例如,如果我想从magic(5)读取中间值,可以像这样做:
M = magic(5);
value = M(3,3);

我希望得到value == 13的值。我想做类似以下其中一种方式的操作:

value = magic(5)(3,3);
value = (magic(5))(3,3);

如何在不使用中间变量的情况下从数组/矩阵中读取值?但是,MATLAB在第一个括号之前出现 Unbalanced or unexpected parenthesis or bracket 的错误提示。


2
我还发现了以下关于这个主题的文章:http://www.mathworks.com/matlabcentral/newsreader/view_thread/280225。有人对此主题有新信息吗?它将被实现吗? - user758294
3
这种语法在Octave中可以正常工作。当我的同事使用MATLAB运行我的代码时,我才发现了这个问题。请注意,不要更改原意。 - sffc
7
MATLAB简介。 - user76284
1
递归提取在Scilab(http://www.scilab.org)6版本及以上版本中也可以使用。 - Stéphane Mottelet
2
在Scilab上,testmatrix('magi', 5)(3, 3)和Octave上的magic(5)(3, 3)都能够完美运行! - Foad S. Farimani
9个回答

419
实际上,你想要的是可以做到的,但你需要使用索引运算符的函数形式。当你使用()进行索引操作时,实际上是在调用subsref函数。因此,即使你不能这样做:
value = magic(5)(3, 3);

你可以这样做:

value = subsref(magic(5), struct('type', '()', 'subs', {{3, 3}}));

难看,但是可行。;)

通常情况下,你只需要将索引步骤更改为函数调用,这样你就不会有两个紧随其后的括号集。另一种方法是定义自己的匿名函数来执行下标索引。例如:

subindex = @(A, r, c) A(r, c);     % An anonymous function for 2-D indexing
value = subindex(magic(5), 3, 3);  % Use the function to index the matrix

然而,说到底,临时本地变量的解决方案更易读,并且绝对是我建议的选择。

32
哎呀,你知道吗!尽管我同意它很丑陋,而且可能比使用 temp-var 的解决方案更难读。但对于令人印象深刻的晦涩 Matlab 知识,我给一个加一分! - second
67
很恶心,但是回答非常清晰。做得好!我应该猜到它有后门。我想我会继续使用临时变量。 - Joe Kearney
33
请记住,中间变量仍然完全创建了。因此,如果目的是通过不必创建临时局部变量来节省内存,那就没戏了。 - Sam Roberts
10
在像Matlab这样的严格求值语言中,你实际上无法避免这种情况。人们想要这样做的主要原因是为了简洁性/可读性,而不是为了节省内存。 - Mechanical snail
7
@SamRoberts:没错,但这确实可以避免你在临时变量上调用 clear 的麻烦(实际上没有人这样做)-- 临时变量往往会停留更长时间。 - Rody Oldenhuis
显示剩余13条评论

146

几天前,Matlab之美这篇好博客文章提供了一些技巧,可能会有所帮助。特别是,使用类似于以下的辅助函数:

paren = @(x, varargin) x(varargin{:});
curly = @(x, varargin) x{varargin{:}};

可以像这样使用paren()

paren(magic(5), 3, 3);

将返回

ans = 16
我认为这比gnovice的答案更快,但我还没有检查(使用分析器!)。话虽如此,你也必须在某个地方包含这些函数定义。我个人已经将它们作为独立函数放在我的路径中,因为它们非常有用。
这些函数和其他函数现在都可以在“功能编程构造”附加组件中使用,可通过MATLAB Add-On Explorer或文件交换获得。

2
这是gnovice答案后半部分的稍微通用一些的版本;同样不错。 - Joe Kearney
myfunc().attr是什么情况? - gerrit
@gerrit,at 命令有什么作用?而且 x.attr() 字段只有在你安装了数据库工具箱后才可用。 - T. Furfaro
@T.Furfaro 嗯?如果myfunc()返回一个包含属性attr的结构体,那么要访问attr目前需要这样做:S = myfunc(); S.attr。问题是我们是否可以有一个辅助函数,类似于parencurly辅助函数,如getattr(myfunc(),'attr')。我不明白这与数据库工具箱有什么关系。 - gerrit
2
@gerrit 抱歉,我完全混淆了(我不知道你的“attr”是任意的 - 在数据库表中有一个明确定义的字段)。我相信你要找的是getfield() - T. Furfaro
显示剩余2条评论

75

你对使用未记录的功能有何感想:

>> builtin('_paren', magic(5), 3, 3)               %# M(3,3)
ans =
    13

或对于单元数组:

>> builtin('_brace', num2cell(magic(5)), 3, 3)     %# C{3,3}
ans =
    13

就像魔术一样 :)


更新:

不好的消息是,上面的黑客方法在 R2015b 版本中不再适用!没关系,那是未记录的功能,我们不能将其作为受支持的特性来依赖它:)

对于那些想知道在哪里找到这种东西的人,请查看文件夹 fullfile(matlabroot,'bin','registry')。那里有一堆 XML 文件列出了各种好东西。请注意,直接调用其中一些函数可能会轻易地崩溃您的 MATLAB 会话。


@RodyOldenhuis:我现在不记得了,我猜我一定是在某个被埋藏的代码中读到的 ;) - Amro
2
冒号(:)操作符必须与英文单引号':'一起使用,以避免出现错误Undefined function or variable "builtin" - Dominik
@Dominik:对,比如你想切片第二列,那么代码就是:builtin('_paren', magic(5), ':', 2)(在某些情况下,直接使用冒号而不是引号,例如在命令提示符中直接运行而不是从函数内部运行时,它可以正常工作。我猜这是解析器中的一个错误!) - Amro
2
我不认为有什么方法可以在这里使用end啊? - knedlsepp
2
@knedlsepp:不幸的是,在这种语法中整个end-trickery都不起作用,您必须在索引中明确指定。(大多数其他列出的答案也适用相同的限制) - Amro

61

至少在MATLAB 2013a中,您可以使用getfield命令:

a=rand(5);
getfield(a,{1,2}) % etc

获取 (1,2) 位置的元素


7
这实际上是个不错的方法。有什么缺点吗? - mmumboss
7
这是未记录的行为,这个功能在未来版本中可能会被取消而没有通知。除此之外没有任何不利影响。 - Daniel
11
截至MATLAB2017b版本,此功能已有文档记录。 - njs
如何获取输出的一列或一行?例如 a(1, :)。我尝试过 getfield(rand(5), {1, 1:5})getfield(rand(5), {1:5, 1}),它们可以正常工作,但不够优雅。 - ZR Han
1
@ZRHan:你可以使用getfield(rand(5), {1, ':'}) - John

17

很遗憾,Matlab不支持像magic(5)(3,3)这样的语法。您需要使用临时中间变量。例如,您可以在使用后释放内存。

tmp = magic(3);
myVar = tmp(3,3);
clear tmp

13
请注意,如果您使用标准方法比较运行时间(即分配结果,然后访问条目),则它们完全相同。
subs=@(M,i,j) M(i,j);
>> for nit=1:10;tic;subs(magic(100),1:10,1:10);tlap(nit)=toc;end;mean(tlap)

ans =

0.0103

>> for nit=1:10,tic;M=magic(100); M(1:10,1:10);tlap(nit)=toc;end;mean(tlap)

ans =

0.0101

在我看来,最重要的是:MATLAB没有指针,你必须接受这一点。


6
如果您创建一个新的函数,它可能会更简单:
function [ element ] = getElem( matrix, index1, index2 )
    element = matrix(index1, index2);
end

然后使用它:

value = getElem(magic(5), 3, 3);

1
但这正是subref所做的...但以更一般的方式。 - Shai
3
是的,更一般化一些,但不够友善... 在我看来有点丑。 - Vugar

4

您最初的表示法是实现这个的最简洁方式:

M = magic(5);  %create
value = M(3,3);  % extract useful data
clear M;  %free memory

如果您在循环中执行此操作,每次都可以重新分配M并忽略clear语句。

8
我同意这个更加简洁,而且在循环中进行清理是个好主意,正如你所说的,但问题具体是关于是否可以避免中间赋值的。 - Joe Kearney
1
clear语句会显著地减缓您的代码运行速度,除非M非常大且您在某些地方内存不足,否则最好将其省略。 - Cris Luengo
@JoeKearney 已经明白了。也许这是因为我在Matlab方面的初学者水平,但每个答案中都会计算出中间值,即使有些只是隐含地计算。这样做是正确的吗?无论如何,感谢您的反馈! - Andreas GS

1

补充Amro的回答,你可以使用feval代替builtin。实际上并没有什么区别,除非你尝试重载操作符函数:

BUILTIN(...)FEVAL(...)相同,只是它会调用原始的内置函数版本,即使已经存在重载的版本(为了使其工作,您必须永远不要重载BUILTIN)。

>> feval('_paren', magic(5), 3, 3)               % M(3,3)
ans =
    13

>> feval('_brace', num2cell(magic(5)), 3, 3)     % C{3,3}
ans =
    13

有趣的是,在Matlab 2013b中,feval似乎比builtin稍微快一点(约3.5%),这很奇怪,因为与builtin不同,feval需要检查函数是否被重载:
>> tic; for i=1:1e6, feval('_paren', magic(5), 3, 3); end; toc;
Elapsed time is 49.904117 seconds.
>> tic; for i=1:1e6, builtin('_paren', magic(5), 3, 3); end; toc;
Elapsed time is 51.485339 seconds.

这其实并不奇怪:MATLAB保留了已定义函数的列表,因此需要搜索的内容并不多。feval执行“正常”的操作,因此可以充分利用该列表。builtin必须在其他地方搜索,因此只能找到内置函数。很可能这种情况没有像“正常”情况那样进行优化,因为为什么要把钱投入到不经常使用的东西上呢? - Cris Luengo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接