第3章 3.4.4 逻辑运算(MATLAB入门课程)

 讲解视频:可以在bilibili搜索“MATLAB教程新手入门篇——数学建模清风主讲”。

MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili

3.4.4.1     逻辑运算函数

在上一节介绍关系运算时,我们提到过:MATLAB中使用逻辑值表示布尔变量,逻辑值1代表真(true),逻辑值0代表假(false)。而逻辑运算就是对逻辑值进行的运算。大部分的编程语言都会涵盖四个最基本的逻辑运算方法:逻辑与、逻辑或、逻辑非和逻辑异或。

下表给出了MATLAB中的定义(注意:下表中的1和0是逻辑值1和0):

注意,上表中函数名和对应的运算符可以执行相同的功能,除了“逻辑异或”没有相应的运算符外,剩下三个运算方法都有对应的运算符。

这四个运算方法的使用方法较为相似,以“逻辑与”为例,大家可以查看“逻辑与”的帮助文档:A & B 对数组 A 和 B 执行逻辑 AND 操作,并返回包含设置为逻辑值 1 (true) 或逻辑值 0 (false) 的元素的数组。如果 A 和 B 在相同的数组位置都包含非零元素,则返回的数组中对应位置的元素将设置为逻辑值 1 (true)。如果不是,则将数组元素设置为 0。and(A,B) 是执行 A & B 的替代方法,但很少使用。(注:该帮助文档来自于MATLAB2023之前的版本,新版本增加对表格类型数据的计算)

从帮助文档可以得知:(1)MATLAB推荐大家直接使用运算符进行计算,因此&、|和~这三个符号的功能大家要牢记,我们主要使用这三个符号而不是对应的函数进行计算。(2)“逻辑与&”是对数组A和B进行计算的,计算时会比较A和B对应位置的元素。数组A和B的大小不一定要完全相同,只需要符合算术运算中介绍的五种兼容模式即可。(3)“逻辑与&”不仅可以作用在逻辑值0和1上,还可以用于普通的数值上,这时候,MATLAB会将非零数值视为逻辑1,将数值零视为逻辑0进行运算。例如:3&5返回逻辑值1,-4&0返回逻辑值0。我们来看两个例子:

有同学可能会有疑问,为什么MATLAB会将非零数值视为逻辑1,将数值零视为逻辑0?这是因为MATLAB在进行逻辑运算之前,在计算机内部自动将数值转换成了逻辑值。我们也可以使用logical函数手动进行转换:

L = logical(A) 将 A 转换为一个逻辑值数组。A 中的任意非零元素都将转换为逻辑值 1 (true),零则转换为逻辑值 0 (false)。复数值和 NaN 不能转换为逻辑值。

举个例子,我们随机生成一个4行3列的矩阵,将矩阵中非0位置的元素转换成逻辑值1,等于0的位置的元素转换成逻辑值0:

特别地,我们还可以使用true和false函数分别创建全为逻辑1和逻辑0的逻辑矩阵。以true函数为例,它的主要用法有两种:(1)true(n)可以生成一个n行n列全为逻辑值1的方阵,特别地,当n等于1时可以简写为true,此时表示一个常量;(2)true(m,n)可以生成一个m行n列全为逻辑值1的矩阵。

除了“逻辑与&”外,剩下的三个逻辑运算函数也可以用于普通的数值上,MATLAB也会将非零数值视为逻辑1,将数值零视为逻辑0进行运算,下面我们举几个例子:

另外,我们有时候也会使用“逻辑与 &”和“逻辑或 |”进行连续运算,例如1 & 2 & 3和0 | 3 | 0的结果都是逻辑1,下面我们再看几个例子:

下面我们再次谈谈运算优先级的问题,MATLAB中的运算符有不同的计算优先级,优先级高的先进行计算,例如3+4*2等于11而不是14,这是因为乘法的优先级高于加法。类似的,关系运算符(例如>、==、~=)的优先级要高于逻辑运算符&和|,例如3 > 4 & 2 > -1的返回结果是逻辑0。大家可以去MATLAB官网查看运算符优先级的帮助文档,但没有必要刻意去记,我们只需要养成一个好的习惯:使用小括号来指定计算的先后顺序,例如我们可以将上面的代码改成(3 > 4) & (2 > -1),这样计算的先后顺序看起来会清楚很多。另外有一个特殊的优先级顺序需要大家了解, & 运算符的优先级要高于 | 运算符。尽管 MATLAB 通常按从左到右的顺序计算表达式,但表达式 a|b&c 按 a|(b&c) 形式计算,因此,对于同时包含 & 和 | 的语句,比较好的做法是使用小括号显式地指定期望的语句优先级。

我们来做一个小练习:随机生成一个具有20个元素的向量,用来表示20名同学的成绩(假设成绩为满分100分的整数制)。请通过代码计算得到一个具有20个元素的逻辑向量,向量中对应位置的元素为逻辑值1时表示该同学的成绩在区间[60,80)内,为逻辑值0时表示成绩在区间[60,80)外。

答案如下:

A =   randi([0,100],1,20)
res = (A   >= 60) & (A < 80)
% 一定要注意:不能写成:60   <= A < 80哦!

如果要找出[0 , 60) ∪ [80 , 100]分的同学呢?

答案如下(下面两种方法都可以):

方法1:(A < 60) |   (A >= 80) 
方法2:~((A >= 60) &   (A < 80))

 

下面我们再来介绍MATLAB中另外两个使用频率很高的逻辑运算符:&&||.

这两个运算符和“逻辑与&”和“逻辑或|”作用相同,但它们有两个非常重要的区别:

(1)&&和||只能对标量(只有一个元素)进行逻辑运算,不能对有多个元素的向量或者矩阵进行运算,而&和|可以。比如我们上面那个练习题,你只能使用&和|进行运算。

(2)&&和||进行逻辑运算时具有短路功能,可以提高运行效率:

  • 计算A && B时,如果A为逻辑0,则B不会被判断,因为最后的结果一定是逻辑0;
  • 计算A || B时,如果A为逻辑1,则B不会被判断,因为最后的结果一定是逻辑1。

          

举个例子:假设a等于10,b等于3,现在要计算:(a+b < 10) && (a/b > 1),那么MATLAB首先会判断前面一项:(a+b < 10),因为这一项计算的结果为逻辑0,所以后面的(a/b > 1)这一项不会被计算,MATLAB会直接返回逻辑0;如果你使用的是:(a+b < 10) & (a/b > 1),那么这两项都会被计算,这样的话效率会低一点。在下一章中,我们会介绍if判断语句和while循环语句,和&、|相比,&&和||在if和while语句中使用频率更高。

 

思考题:前面我们介绍过logical函数,它能将数值转换为逻辑值,但如果输入的是NaN,那么MATLAB会报错,请大家思考:分别运行(10 > 3) | logical(NaN)和(10 > 3) || logical(NaN)的结果是什么?

答案如下:

运行 (10 > 3) | logical(NaN) 会报错,因为前后两项都会被计算,所以当MATLAB运行到后面的logical(NaN)时会报错;运行 (10 > 3) || logical(NaN) 会返回逻辑1,因为前面一项(10>3)返回逻辑1,此时会触发||的短路机制,这时候就会直接返回逻辑1。

3.4.4.2    利用逻辑值引用矩阵的元素

前面我们学过MATLAB中的向量和矩阵,并介绍过如何通过元素所处的位置索引来引用(提取)所需的元素。例如:若a是一个向量,则a(1:2:end)表示提取a中所有奇数位置的元素;若A是一个矩阵,则A(1:2:end, :)表示提取A中所有奇数行的元素。

本小节我们介绍引用矩阵的元素的另一种方式:利用逻辑值。

假设有一个m行n列的矩阵A,我们要提取其指定位置的元素,那么我们可以生成一个和A同样大小的逻辑矩阵L,L中的元素要么是逻辑值1,要么是逻辑值0,其中:等于逻辑值1的元素所处的位置是我们所需要的。接着我们只需要使用命令A(L),就能够在A中提取出指定位置的元素。通常逻辑矩阵L是由一系列的逻辑运算或者条件运算得来的。

举个例子,我们要提取A中所有小于等于3的元素,这里的A是我随机生成的一个矩阵:

这里的L就是通过关系运算得到的一个逻辑矩阵,它的大小和A相同,L中为逻辑值1的位置就是矩阵A中要引用的位置。

注意:命令A(L)得到的结果是一个列向量,有同学会问:为什么A(L)返回的列向量中的元素顺序是3,1,2,而不是其他的顺序?这是因为MATLAB会按照线性索引的顺序来返回提取的元素,就类似于A(:)命令可以将A中的所有元素按照线性索引的顺序返回一个列向量。

以后大家熟练的话,可以直接写成A(A<=3)来提取A中所有小于等于3的元素。再举个例子,如果要提取A中大于等于5且小于8的元素,我们可以使用A( A>=5 & A < 8),千万不能写成A(5 <= A < 8),也不能使用&&运算符:A( A>=5 && A < 8),&&运算符只能用于标量的逻辑运算。

注意,这里有一个易错点:L必须是逻辑矩阵,即里面的0和1都必须是逻辑值,不能是由数字0和1构成的数值矩阵。如果L是数值矩阵,可以使用logical函数进行转换。

练习题:清风班上有20名同学,这20名同学的编号为数字1至20。现在清风老师要随机请一些同学去吃饭,每名同学被抽中的概率都是50%,请帮助清风老师随机抽取这些同学(学过概率论的同学应该能够看出来,每名同学是否能被选中均服从一个伯努利分布,就类似于独立的投20次篮,每次投中的概率都是0.5)。

答案如下:

A   = 1:20;  % 这20名同学的编号为1,2,……,20
x   = randi([0,1],1,20);  % 随机生成20个由0和1构成的随机数
L   = logical(x);  % 转换成逻辑值
A(L)  %   使用逻辑向量L引用A中的元素

为什么上面的答案可行?第二行代码随机生成了一个包含20个元素的行向量x,向量x中的每个元素都是随机生成的0或1,其中0代表同学没有被选中,1代表同学被选中。由于randi函数用来生成随机的均匀分布的整数,因此出现0和1的概率相同,这就满足了每名同学被抽中的概率都是50%。第三行代码使用logical函数将x这个数值向量转换成了逻辑向量,这样才能通过逻辑值对向量元素引用。大家可以自己尝试,如果不使用logical函数进行转换的话,MATLAB就会报错:数组索引必须为正整数或逻辑值。

除了以上方法外,还有一种更为通用的方法,不妨假设每名同学被抽中的概率都是p,其中0≤p≤1,那么我们可以借助下面的代码来抽取这些同学:

更通用的答案:

p = 0.5;  % 每名同学被抽中的概率
A = 1:20;  % 这20名同学的编号为1,2,……,20
r = rand(1,20);  % 随机生成20个在0至1区间上均匀分布的随机数
L = (r < p);  % r和p比较大小
A(L)   % 使用逻辑向量L引用A中的元素

在更通用的答案中,我们通过比较由rand(1,20)生成的、位于[0, 1]区间上的20个随机数r与每名同学被抽中的概率p来构建逻辑数组L。对于每一个元素r(i),如果r(i) < p,则L(i)为逻辑值1,表示第i个同学被抽中;反之,如果r(i) >= p,则L(i)为逻辑值0,表示第i个同学未被抽中。最终,逻辑数组L的逻辑值1和逻辑值0会被用来从原始数组A中提取元素,即A(L)会返回所有被抽中同学的编号。

下面我们再来看一个练习题:随机生成200名同学的考试得分(0-100整数),统计得分大于等于60分的同学人数,并计算得分位于区间[60,80]内的所有同学的平均分。

答案如下:

A   = randi([0,100],200,1);  % 随机生成200名同学的分数
sum(A>=60)   %   大于等于60分的人数
tmp   = A(A<=80 & A >=60);  % 区间[60,80]内的所有分数
mean(tmp)  %   计算平均值

3.4.4.3     使用逻辑值修改或删除矩阵元素

上一小节我们介绍了利用逻辑值引用矩阵的元素,我们也可以对引用的元素进行修改或删除。下面我们直接看例子:

 

从上表最后一个例子可以看出,使用逻辑索引删除矩阵中的元素后,MATLAB会将矩阵中剩下的元素按照线性索引的顺序放入到一个行向量中。

我们再来看一个有趣的问题:缺失值的识别和填补。

举个例子:假设清风老师要连续一周测量早上6点室外的温度,结果清风老师周二和周五睡过了头,那两天的温度没有测量,剩下五天的温度分别是10°、5°、2°、8°和5°,周二和周五的温度成了缺失值。现在清风老师想利用有数据的剩余五天的平均气温来代替周二和周五这两天的温度,于是他计算出这五天的平均气温为(10+5+2+8+5)/5=6°,这时候就完成了对缺失值的填补。

那么我们怎样在MATLAB中实现这个过程呢?我们可以先定义一个向量A用来保存这一周的温度:A = [10 NaN 5 2 NaN 8 5],其中第二个元素和第五个元素为NaN,代表周二和周五的温度数据是缺失的。现在需要大家将A中所有的NaN值替换成所有非缺失值的平均值。

答案只需要一行代码:A(isnan(A)) = mean(A(~isnan(A)))。这里用到了isnan函数,它可以判断数组中的元素是否为不定值NaN,并返回一个和输入的数组大小相同的逻辑数组。

例如,这里的isnan(A)返回的结果就是[0 1 0 0 1 0 0]这个逻辑向量。

有同学会想:为什么不直接用命令A==NaN来找A中的缺失值?这是因为在MATLAB中,NaN相互之间不相等,运行NaN == NaN会输出逻辑值0。

那么,如何找出A中所有非缺失值的元素呢?我们可以对isnan(A)的结果进行“逻辑非”运算,即~isnan(A),然后再利用这个逻辑向量对A进行索引:A(~isnan(A))。

详细的介绍大家可以看本讲义的配套视频。后续章节中我们会更系统地介绍缺失值的知识点,现在只是小试牛刀。

3.4.4.4     all、any和find函数

下面我们介绍三个非常重要的函数,它们的作用请看下表:

其中,all函数和any函数的用法类似,以all函数为例,它的用法如下:

(1)如果A是一个向量,那么当所有元素均为非零值时,all(A)返回逻辑值1 (true),当存在一个或多个元素为零时,返回逻辑值0 (false)。

(2)如果A是一个矩阵,那么all(A,dim) 沿着dim维来判断元素是否全为非零值,dim等于1时沿着行方向来判断每一列是否全为非零值,并将结果返回为一个全为逻辑值的行向量;dim等于2时表示沿着列方向判断每一行是否全为非零值,并将结果返回为一个全为逻辑值的列向量。特别地,当dim等于1时,可以直接简写成all(A)。

(事实上,all函数和any函数的用法和我们之前讲解的sum函数非常像)

 

可以看出,all函数相当于对向量或者矩阵的元素进行‘逻辑与&’运算,只有全为非零值时才返回逻辑值1。而any函数则相当于对元素进行‘逻辑或|’运算,存在至少一个非零值时就会返回逻辑值1。

我们来看any函数的例子:

 

事实上,all函数和any函数很少直接运用在数值矩阵上,它常常配合逻辑矩阵来实现特定的功能。我们来看下面的练习题:

(1)请随机生成一个100行3列的矩阵,用来记录学生的考试成绩:矩阵每一行代表一名同学,每一列代表一门科目的成绩,矩阵中的每个元素都是区间[50,100]内的随机整数。

score = randi([50,100],100,3)

(2)三门科目的成绩都不低于85分的同学可以获得奖学金评选的资格,请指出哪些同学可以获得资格。要求返回一个包含100个元素的逻辑向量,元素为逻辑值1的位置对应的同学有评选资格。

all(score >= 85,2)

(3)请指出哪些同学挂科了,至少有一门科目没过60分就算挂科。要求返回一个包含100个元素的逻辑向量,元素为逻辑值1的位置对应的同学挂科了。

any(score < 60,2)

(4)这三门科目中是否存在科目没有人挂科(所有同学的这一门科目的成绩都高于60分)。要求返回一个包含3个元素的逻辑向量,元素为逻辑1的位置对应的科目表示没有人挂科。

all(score >= 60)

大家应该注意到了,上面问题的答案有一点冗余。例如第二问我们关心的是哪些同学可以获得评选资格,但是答案返回的结果是一个长度为100的逻辑向量,向量中也包含了没有获得评选资格的同学,他们用逻辑值0表示。那么有没有一种方法能够找到这个向量中所有非零元素呢?find函数可以帮助我们实现!下面是MATLAB官方文档对于find函数的介绍:

 

默认情况下,find函数会返回所有非零元素的索引,如果只给find函数一个返回值,那么会返回所有非零元素的线性索引;如果给两个返回值,那么会返回非零元素对应的行和列下标;如果给三个返回值,那么还会返回非零元素构成的向量。另外,大家也可以指定返回前n个非零元素的索引,只需要给定第二个输入参数n,此时会返回前n个非零元素的索引,如果要返回后n个非零元素的索引,那么需要使用find(X,n,'last')。

 

回到上面练习题的第二个问题,我们可以使用下面的命令对代码进行改进,这样就可以返回获得资格的同学的索引:

tmp = all(score >= 85,2);
ind = find(tmp)

练习题(接着上题来):

(5)找出恰好挂了两门科目的同学的编号。

tmp = sum(score < 60,2);  % 每位同学挂科的数目
find(tmp == 2)

(6)找到总分超过260分的同学的编号。

total_score   = sum(score,2); % 计算每位同学的总分
find(total_score   > 260)


  点击下方的CSDN专栏阅读下一篇文章:

MATLAB入门课程专栏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值