Matlab中的数组索引

1. 数组相关函数

前面我们已经介绍了关于向量、数组的一些基本约定,已经涉及了一些数组创建和操作的规则。

函数作用备注
size返回数组的大小
length返回数组的最大维度
ndims返回数组的维度数
numel返回数组的元素个数
zeros创建全零数组
ones创建全一数组
rand创建随机数组
randn创建服从正态分布的随机数组
linspace创建等差数列
logspace创建等比数列

特别还有操作符:,用于生成等差数列,如1:2:10,产生行向量[1,3,5,7,9]

还有一些没有介绍的创建函数,也很常用。

函数作用备注
eye创建单位矩阵
magic创建魔方阵
true创建逻辑1
false创建逻辑0

还有几个函数,是提取部分元素或者组合元素形成新的数组。

函数作用备注
blkdiag创建对角矩阵
diag提取对角线元素
tril提取下三角矩阵
triu提取上三角矩阵
flip翻转数组
fliplr水平翻转数组
flipud垂直翻转数组
cat拼接数组
repmat复制数组
horzcat水平拼接数组
vertcat垂直拼接数组
reshape重塑数组
squeeze去除维度为1的维度
repelem重复数组元素

这里不对这些命令进行详细介绍,需要查看帮助的,请在Matlab命令行中输入help命令,如help eyehelp magic等。要查看详细文档,可以在Matlab命令行中输入doc命令,如doc eyedoc magic等。

这里对数组元素的访问,也称为数组索引,进行一个专门的介绍。

2. 为什么要用括号

很多程序员和初学者在使用Matlab时,会有一个疑问:为什么要用括号来索引数组元素,而不是用方括号?毕竟在C、Python等语言中,我们都是用方括号来索引数组元素的。所以在第一门课程里面都是按方括号来来学习的,这是第一印象。

我在仔细调研这个问题之前就形成了一个观点:那就是Matlab把数组(矩阵)假装成一个对象,这个对象自己是一个函数,索引就是这个函数的意义。

下面,请听我的狡辩。

在Matlab中,函数的调用是用括号的,如sin(x)size(A)plot(x,y)等等。这是Matlab的基本约定,括号是函数调用的标志。那么索引数组的元素呢?也是用括号,如A(1,2)A(1:3)A(1:2:end)等等。是不是很相似?

其次,等后面我会专门写一个函数的约定,里面会讲到,Matlab里面有一个数据构造叫做cell,跟别的语言的元组非常类似,实际上Matlab函数的输入参数和输出值都是cell

比如有个函数size,他可以返回数组的大小,或者返回数组的特定维的大小。

A = magic(3); % 3 x 3
sz = size(A); % [3,3]
sz1 = size(A,1); % 3

args = {A, 1};
sz2 = size(args{:}); % 3

我们再来看高维数组的索引,看看是不是有什么很一样的地方。

A = magic(3); % 3 x 3

A(1,2) % 索引单个元素


A(2:3,1:2) % 索引子矩阵

A([1,2], [1, 3]) % 索引特定元素

那么你们猜猜,是否能够采用上面的cell的方式来索引数组呢?

A = magic(3); % 3 x 3

idx = {1,2};
A(idx{:}) % 索引单个元素

idx = {2:3,1:2};
A(idx{:}) % 索引子矩阵

idx = {[1,2], [1, 3]};
A(idx{:}) % 索引特定元素

很可能你们会发现,这个cell的方式和数组索引的方式是一样的。

一个东西,它看起来像函数,走起来像函数,叫起来也像函数,那么它就(可能)是函数。

以上,就是我的狡辩。实际上,只要接受了这个设定,你会发现Matlab的数组索引是非常方便的,而且非常强大。

3. 索引的种类

Matlab中的数组索引可以按两种方式进行:

  1. 位置索引
  2. 逻辑索引

位置索引很简单,就是通过前面给出的cell的方式,指定数组的位置,如A(1,2)A(1:3)A(1:2:end)等等,你就想象成函数调用,按照低维至高维来制定下标范围,每个维度的下标范围必须是一个整数、整数列表、colon列表(😃。这个下标范围就是位置索引,每个索引的值都应该在 [ 1 , size ( A , i ) ] [1, \text{size}(A,i)] [1,size(A,i)]之间(请思考这句话是不是一定对。)。值得注意的是,这个位置索引的数量可以超过维数(ndims),但是超过维数的数值只能是1。(请思考为什么?

还有一个可以注意的事情是,可以降维索引,也就是不给足ndims个下标,此时,最后一个下标就会成为当前维到最高的非1的维的乘积。这个特性在后面的还会专门提到。

逻辑索引也非常简单,逻辑索引就是调用这个数组函数时,给的参数不是整数下标,而是逻辑值或者逻辑值组成的数组,这个数组索引的所有参数构成的cell元组给定的位置,true对应选中,false对应不选中。

看看上面的分析就知道,这两个索引概念跟降维索引交叉起来会形成四种索引方式:

  1. 线性(降维)位置索引
  2. 线性(降维)逻辑索引
  3. 多维位置索引
  4. 多维逻辑索引

值得注意的是,帮助文档只给出线性位置索引和多维逻辑索引的例子,但是实际上上面这几种复杂的情况都是存在的、合法的索引方式(数组函数调用方式)。

那么?我们提到的前面的一个左值和右值个数numel必须相等的约定,以及左值设定右值的对应关系应该如何取得呢?(请思考什么是对应关系

A = B
% numel(A) == numel(B)
% 请思考如何赋值?

Matlab必须以某种具备一致性的方式,访问A 和B的元素,考虑到上面我们已经搞出了这么多索引方式,这个对应关系应该如何取得呢?

4. 索引的规则

对于A(idx)这个表达式,这里的idx可以是任何合法的索引方式,那么这个表达式的值应该是什么呢?

这个问题的答案非常简单,我们已知的信息

  1. size(A)
  2. idx

那么我们选择一个什么样的方式最为合情合理呢?有没有一种索引是很容易在字面上是唯一的呢?这个问题随便拍脑袋就知道,那就是:

1:numel(A)

这个列向量及其中的部分元素构成的数组,可以唯一的确定A中元素的子集。按照A(:)这个方式,任何N维数组都可以被展开成一个列向量,这个列向量的元素就是原数组的元素,这个列向量的索引就是原数组的索引。

Matlab提供了一组函数,用于把任何一种索引方式转换成线性位置索引,这个函数就是sub2ind,这个函数的调用方式是:

ind = sub2ind(size(A), idx1, idx2, ..., idxN)

这跟下面的调用是一模一样的,我们在函数的讲解中会详细介绍。

ind = sub2ind(size(A), idx{:})

这里的idx1, idx2, ..., idxN就是idx的元素,可以是上面若干中索引的组合,当然必须确保idxsize(A)的范围是合法的(包括降维后的合法性)。

有了这个函数之后,就可以尽情地发掘Matlab的索引方式了。最终访问数字的元素,都是依靠这个函数返回值的唯一性来保证,通过这个函数(类似的逻辑),也能够保证左值和右值的对应关系。

相应地,还有一个作用相反地函数ind2sub,这个函数的调用方式是:

[idx1, idx2, ..., idxN] = ind2sub(size(A), ind)

这里左值的个数自然数个,当个数少于ndims时,最后一个下标就会成为当前维索引与所有高维维数的乘积(请思考为什么);当个数多于ndims时,多出来的下标全部都是1(这在语义上是合理的,因为这些下标对应的维度是1)。

这里举一个例子。

A = rand(3, 4, 5); % 3 x 4 x 5
s = size(A); % [3, 4, 5]

ind = sub2ind(s, 2, 3, 4); % 44  如何计算得到这个值?

[i1, j1, k1] = ind2sub(s, ind); % 2, 3, 4

[i2, j2] = ind2sub(s, ind); % 2, 15

[i3] = ind2sub(s, ind); % 44

这里的ind是如何计算得到的呢?这个问题是一个很好的思考题,可以帮助你理解sub2ind函数的作用。

ind = ( 4 − 1 ) × 3 × 4 + ( 3 − 1 ) × 3 + 2 = 44 \text{ind} = (4-1)\times 3 \times 4 + (3-1) \times 3 + 2 = 44 ind=41×3×4+(31)×3+2=44

我们的计算有两个要点:

  1. 从高维开始算,每个维度的下标都是从1开始的,所以要减去1;
  2. 不足一个高维的,递归计算下去。

此外ind2sub中下标可以是一个向量,这样就可以一次性计算多个位置的下标。此时,每个位置的向量长度必须相等,否则会报错。

A = rand(3, 4, 5); % 3 x 4 x 5
s = size(A); % [3, 4, 5]

ind = [2, 3, 4]; 

[i1, j1, k1] = ind2sub(s, ind)% i1 = [2,3,1], j2=[1,1,2], k1=[1,1,1]


ind = sub2ind(s, i1, j1, k1); % [2,3,4]

5. 逻辑索引的有趣之处

逻辑索引一般而言,会采用一个跟被索引矩阵大小相同的逻辑矩阵,这个逻辑矩阵的元素值为true或者false,true对应的位置会被选中,false对应的位置会被忽略。

那么,跟上面的位置索引一样,如果逻辑索引降维了呢?如果逻辑索引的每个维长度小于被索引矩阵的对应维度呢?

A = rand(3, 4, 5); % 3 x 4 x 5
s = size(A); % [3, 4, 5]

idx = [true, false, true]; % 1 x 2

B = A(idx); % 1 x 2,对应的元素是A(1,1), A(1,3)


idx2 = {[true, false, true], [true, false, true]}; % 2 x 2

C = A(idx2{:}) % 2 x 2,对应的元素是A(1,1), A(1,3), A(3,1), A(3,3)

这里降维和索引是怎么样的呢?这个问题是一个很好的思考题。

6. 结论

  1. Matlab的数组索引看起来非常像是函数调用,也确实可以当作函数调用来理解;
  2. 可以利用位置索引和逻辑索引来访问数组的元素;
  3. 索引的一致性可以通过sub2ind函数来验证。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐