如何在MATLAB中迭代访问n维矩阵中的每个元素?

87

我有一个问题。我需要在MATLAB中遍历n维矩阵中的每个元素。问题是,对于任意数量的维度,我不知道如何做到这一点。我知道我可以这样说:

for i = 1:size(m,1)
    for j = 1:size(m,2)
        for k = 1:size(m,3)

等等,但是否有一种方法可以对任意维数进行操作?


13
Matlab术语注释:Matlab有一小部分核心数据类型。最重要的是:结构体、矩阵和单元数组。当引用矩阵的各个部分时,通常使用术语“元素”,并将术语“单元”保留用于引用单元数组的各个部分。尽管两者都是N维数据结构,但单元数组和矩阵具有许多语法和语义上的差异。 - Mr Fooz
3
我可以问一下您需要迭代的原因吗?也许有一种“向量化”的方法可以代替迭代... - Hosam Aly
8个回答

93

你可以使用线性索引来访问每个元素。

for idx = 1:numel(array)
    element = array(idx)
    ....
end

如果您不需要知道 i、j 和 k 的值,则这很有用。然而,如果您不需要知道索引值,那么使用 arrayfun() 可能更好。


1
此外,如果您因某种原因想要恢复索引,仍然可以使用这两个简单的命令:I = cell(1, ndims(array)); [I{:}] = ind2sub(size(array),idx); - knedlsepp

36
在Matlab中,线性索引数组的概念非常重要。在MATLAB中,数组实际上只是一个元素向量,在内存中串联起来。MATLAB允许您使用行和列索引或单个线性索引。例如,
A = magic(3)
A =
     8     1     6
     3     5     7
     4     9     2

A(2,3)
ans =
     7

A(8)
ans =
     7

我们可以通过展开数组到向量来查看元素在内存中的存储顺序。
A(:)
ans =
     8
     3
     4
     1
     5
     9
     6
     7
     2

正如您所看到的,第8个元素是数字7。实际上,find函数将其结果作为线性索引返回。

find(A>6)
ans =
     1
     6
     8

结果是,我们可以使用单个循环依次访问一般的n维数组中的每个元素。例如,如果我们想要对A的元素进行平方(是的,我知道有更好的方法),可以这样做:

B = zeros(size(A));
for i = 1:numel(A)
  B(i) = A(i).^2;
end

B
B =
    64     1    36
     9    25    49
    16    81     4

在许多情况下,线性索引更有用。使用sub2ind和ind2sub函数可以在线性索引和二维(或更高维)下标之间进行转换。

线性索引通常适用于matlab中的任何数组。因此,您可以将其用于结构、单元数组等。线性索引唯一的问题是当它们变得太大时。MATLAB使用32位整数来存储这些索引。因此,如果您的数组中的元素总数超过2^32个,则线性索引将失败。仅当您经常使用稀疏矩阵时,偶尔会出现此问题。 (虽然我没有使用64位MATLAB版本,但我相信对于那些幸运的人来说,该问题已得到解决。)


在64位MATLAB中,索引确实可以正确地使用64位下标。例如:x = ones(1,2^33,'uint8'); x(2^33)会按预期工作。 - Edric
@Edric - 当然,自从我发表那个声明以来,这种行为肯定已经在多年(和许多版本)中发生了变化。不过还是谢谢你的检查。 - user85109
直到我评论之后,我才意识到这个答案有多老 - 这个问题只是出现在我的RSS订阅中,我甚至没有注意到我也回答了它! - Edric

15

正如其他答案中所指出的那样,您可以使用矩阵 A(任意维度)中的线性索引1numel(A)在单个for循环中迭代所有元素。还有一些函数可以使用:arrayfuncellfun

首先假设您有一个要应用于A每个元素的函数(称为my_func)。您首先创建一个函数句柄以引用此函数:

fcn = @my_func;

如果A是任意维度的矩阵(类型为double、single等),您可以使用arrayfunmy_func应用于每个元素:
outArgs = arrayfun(fcn, A);

如果A是一个任意维度的单元数组,您可以使用cellfunmy_func应用于每个单元格:

outArgs = cellfun(fcn, A);

函数my_func必须接受A作为输入。如果my_func有任何输出,这些将被放置在outArgs中,其大小/维度与A相同。

关于输出的一个注意事项... 如果my_func在操作A的不同元素时返回不同大小和类型的输出,则必须将outArgs转换为单元格数组。这是通过调用arrayfuncellfun并使用额外的参数/值对来完成的:

outArgs = arrayfun(fcn, A, 'UniformOutput', false);
outArgs = cellfun(fcn, A, 'UniformOutput', false);

13

另一个技巧是使用 ind2subsub2ind。结合 numelsize,这可以让您做一些像下面这样的事情,它创建了一个N维数组,然后将所有“对角线”元素设置为1。

d = zeros( 3, 4, 5, 6 ); % Let's pretend this is a user input
nel = numel( d );
sz = size( d );
szargs = cell( 1, ndims( d ) ); % We'll use this with ind2sub in the loop
for ii=1:nel
    [ szargs{:} ] = ind2sub( sz, ii ); % Convert linear index back to subscripts
    if all( [szargs{2:end}] == szargs{1} ) % On the diagonal?
        d( ii ) = 1;
    end
end

+1 为展示 MATLAB 如何打破鸭子类型而给予好的例子。 - Phillip Cloud

1

这些解决方案比使用numel更快(约快11%);)

for idx = reshape(array,1,[]),
     element = element + idx;
end

或者

for idx = array(:)',
    element = element + idx;
end

更新:感谢 @rayryeng 检测上一个答案中的错误


免责声明

由于基本的打字错误(请参见下面的评论流以及编辑历史记录 - 特别是查看此答案的第一个版本),此帖所引用的时间信息是不正确和不准确的。买家自负


1
1:array(:)等同于1:array(1)。这不会遍历所有元素,这就是为什么您的运行时间很快的原因。此外,rand生成浮点数,因此执行1:array(:)将产生一个空数组,因为您的语句试图找到一个初始值为1,结束值为0到1之间的浮点数(选择顶端的数字),以递增的步长1递增的递增向量。不存在这样的可能向量,导致一个空向量。您的for循环不会运行,所以您的声明是错误的。-1票。抱歉。 - rayryeng
@rayryeng,你说的不对。array(:)并不等同于1:array(1)。它更像是reshape(...) - mathcow
@rayryeng Matlab R2013a + Linux - 它可以工作!;) 我也刚刚运行了那段代码。 - mathcow
在创建array后,在您的命令提示符中键入1:array(:)。您得到了一个空矩阵吗?如果是,则您的代码无法正常工作。我留下我的投票,因为您提供了错误的信息。 - rayryeng
@rayryeng 我明白了!是的,你是对的,抱歉我争论得有些愚蠢。 - mathcow
没问题。现在你的答案是正确的……虽然与其他答案没有太大区别。我会取消我的踩票。 - rayryeng

1

你可以使用递归函数来完成这项工作

  • L = size(M)
  • idx = zeros(L,1)
  • length(L) 视为最大深度
  • 循环 for idx(depth) = 1:L(depth)
  • 如果你的深度是 length(L),则进行元素操作,否则使用 depth+1 再次调用该函数

如果你不需要评估大部分点,那么它可以节省很多时间,但如果你想检查所有点,它不如矢量化方法快。


-1

你想模拟n层嵌套的for循环。

遍历n维数组可以看作是增加n位数字。

在每个维度上,我们有与该维度长度相同的数字。

例如:

假设我们有一个数组(矩阵)

int[][][] T=new int[3][4][5];

在“for循环符号”中,我们有:

for(int x=0;x<3;x++)
   for(int y=0;y<4;y++)
       for(int z=0;z<5;z++)
          T[x][y][z]=...

要模拟这个过程,您需要使用“n位数字表示法”

我们有一个三位数,第一位有3个数字,第二位有4个数字,第三位有5个数字

我们需要增加这个数字,以便得到序列

0 0 0
0 0 1
0 0 2    
0 0 3
0 0 4
0 1 0
0 1 1
0 1 2
0 1 3
0 1 4
0 2 0
0 2 1
0 2 2
0 2 3
0 2 4
0 3 0
0 3 1
0 3 2
0 3 3
0 3 4
and so on

因此,您可以编写增加这种n位数字的代码。您可以以任何数字值开始并通过任何数字增加/减少数字的方式进行操作。这样,您可以模拟在表格中某处开始并不在结尾处结束的嵌套for循环。

但是,这不是一项容易的任务。不幸的是,我无法帮助您解决matlab符号问题。


-1

如果你深入了解size的其他用途,你会发现你实际上可以得到每个维度的大小向量。这个链接展示了相关文档:

www.mathworks.com/access/helpdesk/help/techdoc/ref/size.html

获取大小向量后,迭代该向量。类似这样(请原谅我的语法,因为我自从大学以来就没有使用过 Matlab):
d = size(m);
dims = ndims(m);
for dimNumber = 1:dims
   for i = 1:d[dimNumber]
      ...

将此转换为实际的Matlab合法语法,我认为它会做你想要的。

另外,你应该能够像这里描述的那样进行线性索引。


我无法完全看出循环的排序如何遍历矩阵的所有元素。例如,如果您有一个3x4的矩阵(共12个元素),您的内部循环只会迭代7次。 - gnovice
它应该遍历矩阵的每个维度。外部循环遍历维度,内部循环遍历该维度的大小。至少这是想法。正如其他人所说,如果他只想要每个单元格,线性索引是最好的。如果他想遍历每个维度,他将不得不做类似于这样的事情。 - Erich Mirabal
同样的,感谢您的编辑。我的链接有点复杂,使用通常的链接方式就无法正常工作。此外,为了扩展我的陈述:他仍然需要进行很多其他指数跟踪(例如使用计数器或类似的东西)。我认为您或安德鲁的方法对于我认为他试图做的事情会更容易些。 - Erich Mirabal

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