Matlab中的逻辑数组索引
1. 思考题和思考题的复杂化
上一篇数组索引最后,有一段代码:
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)
这里降维和索引是怎么样的呢?
其实这个问题还可以变得更复杂:
idx2 = {[true, false, true], [true, false, true, false, true, true]};
A(idx2{:})
这样又会是什么结果呢?先让我们回到逻辑数组索引的基本概念和基本约定,本文最后,我们给出上面思考题的答案。
2. 逻辑数组索引的基本概念
逻辑数组索引是一种非常灵活的索引方式,它的基本概念是用一个逻辑数组来选择数组中的元素。这个逻辑数组的长度必须和被索引的数组的长度相同(或者小于被索引数组),这个逻辑数组的元素是true或者false,true表示选择这个位置的元素,false表示不选择这个位置的元素。
我们先看基本的例子:
A = magic(3);
B = magic(3)';
inx = A < B;
A(inx)
% ans = [4; 1; 7]
B(inx)
% ans = [6; 3; 9]
这里通过逻辑操作符<得到了一个逻辑数组inx,然后通过逻辑数组索引,得到了A和B中对应位置的元素。最终返回的结果是A和B中对应位置的元素,并构成一个列向量。
这也符合前面所说的约定,计算得到的是列向量。
3. 逻辑数组索引的产生
说到这里,就必须要看看Matlab中的逻辑值到底是什么。Matlab的逻辑值是logical类型,它是一种特殊的数值类型,只有两个值:1和0,分别代表true和false,它的大小就只有1Bytes。对应操作逻辑值的四个基本函数:
logical:将数值转换为逻辑值,跟输入矩阵相同的size;islogical:判断是否是逻辑值;true:返回逻辑值true,可以是矩阵;false:返回逻辑值false,可以是矩阵。
这几个函数的具体调用方式和作用这里不赘述,参考help和doc即可。
通常,产生逻辑数组的方式有:
- 逻辑操作符:
<,<=,>,>=,==,~=; - 逻辑计算符号:
&,|,~; - 逻辑函数:
logical,true,false; - 判断型的函数:
isxxxxx,包括islogical,isnumeric,ischar等等。 - 自己定义返回逻辑值的函数。
在Matlab中,针对矩阵运算有一个特殊约定。
针对单个元素的计算可以扩展到矩阵的每个元素。
像上面的逻辑运算符,可以作用到单个元素,也可以作用到矩阵的每个元素。这个特性在逻辑数组索引中非常重要。例如,矩阵中所有大于5的元素:
A = magic(3);
% A = [8, 1, 6; 3, 5, 7; 4, 9, 2]
inx = A > 5;
% inx = [0, 0, 1; 1, 1, 0; 0, 1, 1]
比如自己定义返回逻辑值的函数:
function y = isodd(x)
y = mod(x, 2) == 1;
end
这样就可以得到一个逻辑数组:
A = magic(3);
inx = isodd(A);
% inx = [1, 0, 1; 1, 0, 1; 0, 1, 0]
值得注意的是,矩阵的逻辑索引可以作为左值,也可以作为右值。这在Matlab里面是常规操作。
A = magic(3);
inx = A > 5;
A(inx) = 0;
A(~inx) = 1;
A % [0, 1, 0; 1, 1, 0; 1, 0, 1]
4. 工程应用约定
当我们常规使用逻辑数组索引时,我们通常约定:
逻辑数组索引由被索引的数组计算得到,逻辑数组的大小和被索引的数组的大小相同。
这一点对于我们把逻辑数组索引的结果作为左值和右值时都能很好的保证语义的一致性。在实际进行工程计算时,这也是一个很好的约定。
通过遵守上述预定来使用逻辑数组索引,我们能够很好的把循环+分支的程序改成更加简洁的表示形式,更好地把Matalb当做一个计算器使用。
前面那个例子把矩阵中大于5的元素置为0,小于等于5的元素置为1,使用常规的程序设计语言,我们需要按照如下的方式来实现:
A = magic(3);
for i = 1:3
for j = 1:3
if A(i, j) > 5
A(i, j) = 0;
else
A(i, j) = 1;
end
end
end
两层循环、判断分支,对于程序员而言,这样的程序是很常见的。但是对于Matlab而言,这样的程序是不够简洁的。我们可以并且习惯通过逻辑数组索引来实现。
这个计算也跟Matlab中大多数操作符、函数能够对矩阵的每个元素进行操作的特性是一致的。
对于某些同时对于矩阵和矩阵元素有意义的操作符和运算,Matlab约定了采用.来表示对矩阵的每个元素进行操作。
A = magic(3);
1 ./ A % [0.125, 1, 0.1667; 0.3333, 0.2, 0.1429; 0.25, 0.1111, 0.5]
A .^ 2 % [64, 1, 36; 9, 25, 49; 16, 81, 4]
除法、乘法、乘方,对于矩阵和矩阵元素的计算有不同含义。
此外,逻辑数组索引还有一个约定。
逻辑数组索引的结果是列向量。
这在Matlab中也是很有一致性的约定。这个约定也是为了保证逻辑数组索引的结果能够作为左值和右值使用时的一致性。
5. 不太常见的逻辑数组索引
现在可以回到前面的思考题,这是一种不常见也不常用的逻辑数组索引方式。我们把它写到二维数组,即矩阵,的例子中,更容易看到这种逻辑数组索引的特性。
A = magic(3);
idx = {[true, true], [true, false, true]};
从结果可以看到:
>> A, A(idx{:})
A =
8 1 6
3 5 7
4 9 2
ans =
8 6
3 7
这个结果是很有意思的。实际上,这个计算的结果大概是这样的。
首先,我们把idx展开,得到:
idx{1}(:) & idx{2}
% [1, 0, 1; 1, 0, 1]
这个情况下,最终索引的结果是把展开的逻辑数组和被索引的数组在左上角对齐,然后按照逻辑数组的true和false来选择元素,构成一个数组(并非是列向量)。
而我们如果把idx展开成idx{1} & idx{2}(:)来进行索引,结果是不一样的。
A(idx{1}(:) & idx{2})
% [8, 3, 5, 9]
此时,最终索引结果是按照列向量的方式来选择元素,这个语义是具备一致意义的。也就是前面我们给出的约定,索引在转换为列向量的基础上具备统一性。哪怕是索引逻辑数组与被索引数组的长度、维数不一致,也是按照列向量的方式来选择元素。
但是,多维逻辑数组索引A(idx{:})在形状不一致时的索引不具备语义的一致性。建议不要在实际的工程计算中使用这种索引方式,因为这种索引方式的结果是不确定的,准备给MathWorks提出一个建议,对这种索引方式进行限制或者更改为更加具备一致性的索引方式。
6. 总结
- Matlab的逻辑值是一种特殊的数值类型,只有两个值:1和0,分别代表
true和false,它的大小就只有1Bytes。 - 逻辑数组索引是一种非常灵活的索引方式,它的基本概念是用一个逻辑数组来选择数组中的元素。
- 逻辑数组索引的产生方式有:逻辑操作符,逻辑计算符号,逻辑函数,判断型的函数,自己定义返回逻辑值的函数。
- 工程应用约定:逻辑数组索引由被索引的数组计算得到,逻辑数组的大小和被索引的数组的大小相同。最好一直采用这种方式来对数组进行索引。