005_index_in_Matlab中的数组索引
前面我们已经介绍了关于向量、数组的一些基本约定,已经涉及了一些数组创建和操作的规则。特别还有操作符,用于生成等差数列,如,产生行向量。还有一些没有介绍的创建函数,也很常用。还有几个函数,是提取部分元素或者组合元素形成新的数组。这里不对这些命令进行详细介绍,需要查看帮助的,请在Matlab命令行中输入命令,如,等。要查看详细文档,可以在Matlab命令行中输入命令,如,等。这里对数组元素的访问,也称
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 eye
,help magic
等。要查看详细文档,可以在Matlab命令行中输入doc
命令,如doc eye
,doc 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中的数组索引可以按两种方式进行:
- 位置索引
- 逻辑索引
位置索引很简单,就是通过前面给出的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对应不选中。
看看上面的分析就知道,这两个索引概念跟降维索引交叉起来会形成四种索引方式:
- 线性(降维)位置索引
- 线性(降维)逻辑索引
- 多维位置索引
- 多维逻辑索引
值得注意的是,帮助文档只给出线性位置索引和多维逻辑索引的例子,但是实际上上面这几种复杂的情况都是存在的、合法的索引方式(数组函数调用方式)。
那么?我们提到的前面的一个左值和右值个数numel
必须相等的约定,以及左值设定右值的对应关系应该如何取得呢?(请思考什么是对应关系)
A = B
% numel(A) == numel(B)
% 请思考如何赋值?
Matlab必须以某种具备一致性的方式,访问A 和B的元素,考虑到上面我们已经搞出了这么多索引方式,这个对应关系应该如何取得呢?
4. 索引的规则
对于A(idx)
这个表达式,这里的idx
可以是任何合法的索引方式,那么这个表达式的值应该是什么呢?
这个问题的答案非常简单,我们已知的信息
- size(A)
- 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
的元素,可以是上面若干中索引的组合,当然必须确保idx
在size(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=(4−1)×3×4+(3−1)×3+2=44
我们的计算有两个要点:
- 从高维开始算,每个维度的下标都是从1开始的,所以要减去1;
- 不足一个高维的,递归计算下去。
此外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. 结论
- Matlab的数组索引看起来非常像是函数调用,也确实可以当作函数调用来理解;
- 可以利用位置索引和逻辑索引来访问数组的元素;
- 索引的一致性可以通过
sub2ind
函数来验证。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)