从零开始学习PYTHON3讲义(五)

while循环和棋盘麦粒问题

《从零开始PYTHON3》第五讲

​上一节课重点学习了字符串,并且传递了一个重要的理念,就是程序要对开发人员自己和用户都足够友好。在这个过程中,利用字符串给出充分、完整、准确的提示是非常重要的一部分。

​在Python可以处理的不同数据类型中,每种数据类型都有自己特色的运算方式,比如我们上一节课对比过的数字类型和字符串类型的运算:

#数值的运算
>>> 123*3
369
#字符串的运算
>>> "123"*3
'123123123'

​两者的计算方式截然的不同,又具有自己的特点和不同的应用场景。这里讲这个例子,并不只是想让大家复习上一讲的课程。而是想让大家思考一下计算机最擅长的工作是什么?

​没错,相信大多数人都想到了,计算机最擅长的,一是计算,二是重复。至于绘图、音乐、视频等等所谓的高端应用,不过是计算、重复的各种复杂组合。

​计算在第二、第三讲我们已经说过很多了,后面还会涉及到更高级的一些计算类应用。“重复”则是今天要说到的重点。


While循环

​第三讲的时候我们学过了计算机执行顺序的问题。

每个Python程序都是从第一行开始,顺序执行,直到程序的最后一句。其中碰到函数定义的时候,会“定义一个函数”,而不是“执行一个函数”。函数真正执行会在函数被调用的时候。

​While循环则是让计算机对某一段的程序代码在限定条件下重复执行的手段。我们来看一个简单的例子来帮助我们理解:

i = 1
while i <= 999:
   i = i + 1

​第一行,我们定义了一个变量i,并为它赋一个数字类型的值1。
第二行是while循环的条件部分,用于控制进入循环和继续循环的条件。简单说,就是当条件满足的才开始循环,并且不断循环下去,直到条件不再被满足。
“<=”是条件,表示“小于等于”,同样是因为计算机中没有传统用的“≤”符号,所以采用了变通的写法。关于条件,或者说条件逻辑,我们会在后面详细讲解。
第三行是一条赋值语句,第二讲我们讲到变量的时候已经强调过,“=”是赋值操作符,表示把右侧表达式的结果值,赋给左侧的变量。
不要跟数学的等式弄混。在这里则是把i当前值,加1的计算结果,赋值给变量i,这时候i的值变成了新的值,也是刚才的计算结果。 我们是头一次见到这种写法,但只要弄明白这个是赋值语句,不是等式,你就不会困惑了。

​这是一个极度简化的循环模型,第一行可以称为初始值,通常这个初始值应当满足循环开始的条件;
第二行称为循环的条件判断,用于控制循环的开始和结束;
第三行称为循环体,循环体应当是循环真正工作的部分,因为简化,在这个例子中我们看不到有意义的工作。i=i+1则让循环持续,并最终能够不再满足循环继续的条件,从而退出循环。否则循环会永无止境的继续下去,这被称为“死循环”,也是计算机软件“崩溃”、“死机”最常见的原因。

​为了帮助理解,我们来看一个循环的流程图。
流程图是研究、分析程序结构的时候,公认非常有效的一种手段。建议你也学习画,初学者不用纠结流程图的样式,而是用这种方式帮你分析程序逻辑方面的问题。

flowChart1

​弄明白了循环的逻辑含义,我们再来看一下while循环语法上的特点,我们对比一下函数定义的语法:

#函数定义
def 函数名(参数):
    函数体
#while循环
while 循环条件:
    循环体

​上面这种描述程序逻辑的方法,看起来结构清楚,能反映出来想描述的程序问题,但并不能执行。这种方式叫做“伪代码”,是跟“程序流程图”一样用来分析、研究程序逻辑的方法,我们以后还会用到。

​上面这个对比中,你能感觉到一些Python语法的逻辑规律。比如,都是用某个关键字开始,来引导整个程序块,函数定义是用def,while循环是用while;接着是各自特色的东西,比如函数名、参数还有循环条件,相似的,都是是用冒号“:”来结尾第一行,并分割下面的函数体、循环体部分;后面甭管是函数体还是循环体,都是缩格书写,缩格的结束代表整个程序块的完成。

​循环体中的赋值操作值得重点说一下。前面已经说过了,通过对可以影响循环条件的变量进行赋值,从而让循环本身有机会退出循环,这是很重要的一个工作。这种赋值改变循环条件,几乎在所有的循环中都会用到。所以这种通过对自身的改变完成对自身赋值的方式,又延伸出了一种简易的写法:

原有写法: 简易写法:
i = i + 1 i += 1
i = i -1 i -= 1
i = i * 2 i *= 2
i = i / 2 i /= 2

​请看上表中,左侧是规范的写法,右侧是化简后的写法,使用起来会更方便。本质上,这是增加了+=、-=、*=、/=,四种运算赋值符,属于保留字的一种。通常我们见到的加减乘除运算符都是一个字符,这里是2个字符,看起来不习惯而已。


练习时间

​请使用while循环的方法,求整数1、2、3……直到100的和。

请先自己思考10分钟,可以用流程图或者伪代码,有了比较明确的思路再向下看。


​我们来看代码:

#求整数1-100的和

#求和的结果保存在c,一开始是0
c = 0
#i保存整数1循环到100
i = 1
#进入及继续循环的条件就是i<=100
while i <= 100:
    c = c + i #求和一次
    i = i + 1 #下一个整数,2/3/4...
#显示结果
print("整数1-100的和为:",c)

​程序中,我们使用了专门的一个变量c来保存累加的和,一开始没有开始累加,所以c是0。变量i通过循环的方式,来模拟整数从1开始,每次加1,直到100的变化。循环的主体c=c+i,则是在每次循环中,进行一次求和的操作。最后缩格结束,表示循环的结束,使用print函数打印出来求和结果。运算结果是:

整数1-100的和为 5050

​作为练习,你可以试试把循环中的两次赋值,用刚才讲过的简写的方式来试试。


条件判断(逻辑判断)

​对于一个循环来说,循环主体当然是循环的目的,所以通常也是循环的重点。但是在循环编写的时候,仔细的思考和设置循环的开始条件和结束的条件,才是编写循环的重点。而条件,通常都是由“逻辑判断”来完成的。

​不同于数字和字符串的千变万化,逻辑条件只有“符合条件”和“不符合条件”两种情况。
前者的同义词可以为“是”、“真”、“对”,后者的同义词是“否”、“假”、“错”。
这种只有两个值的类型,叫“布尔类型”,相关的运算叫“布尔运算”。在Python中,提供了数字(Number)类型的子类型(bool)来代表此类数据。bool类型只有两个值,True表示真,False表示假。
因为bool是Number的子类型,所以如果把bool套用到Number类型中,1就代表真,0就代表假。我们用表格把这种关系再加深一下印象:

符合条件 不符合条件
true false
1 0
“abc”(有内容) ”“(无内容,空串)

​表格中最后一行,是字符串类型。对应到bool类型的情况,通常应当比较少用。但你得知道,如果字符串为空代表False,有字符,甭管是什么字符,都是True。

​既然布尔类型,同数字、同字符串,都有对应的关系,有必要单独独立出来一种数据类型来增加学习量吗?还真的是很有必要,我们来看一个例子。

​比如在一个学生信息表中,在性别一栏,我们可以使用“男”、“女”,也可以用数字1、2。看起来也可以很完美的实现我们的目的,但其实这种做法很不可取。
你想象一下在表格的输入中,有人输入成了“男生”,意思没有变,但这一点小的改变,可能让计算机无所适从。比如数字敲错成了“3”,计算机同样也就无法知道这代表的究竟是什么性别。
如果使用布尔变量,isMan=True代表男生,刚才碰到的那些问题,都不会出现。

​此外布尔运算作为数学中重要的一个分支,有完备的理论体系,在计算机中也有计算速度快、兼容性好的优点。

​刚才讲的是“逻辑”的表达方式,下面看看逻辑判断的方式:

比较运算符 含义
> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于(注意同赋值操作=区分)
!= 不等于

​上表中,大于、大于等于、小于、小于等于都好记。逻辑相等的判断,要跟赋值操作的等号区别开,因为这是完全不同的运算符,或者说是不同的Python关键字。
不等于符号,同样是由于计算机中没有“≠”符号的原因进行了合理的变化。这些都是运算符,运算符不一定只有一个字符。

​下面我们来看几个逻辑判断的例子:

逻辑判断表达式 结果
1 < 2  
1 > 2  
2.2 != 2.1  
“a” > “b”  
“bcd” < “bd”  
a=”hello”  
a == “hello”  
2 = 2  

​请思考后,在本讲结尾看答案。

​除了这些常用的逻辑判断,Python还有自己的特色的一种判断方式,叫做连续判断,这是其它常见的程序语言不具备的:

逻辑判断表达式 结果
1 < 2 < 7 True
1 > 2 > 1 False

​连续判断经常用于对一个数据进行范围界定性判断,所以经常也被称为“范围判断”。


挑战:棋盘麦粒问题

在古代有一个国王,他拥有至高无上的权力和难以计数的财富。但是权力和财富最终使他对生活感到厌倦,渴望着有新鲜的刺激。
某天,一位老人带着自己发明的国际象棋来朝见。国王对这新奇的玩意非常喜欢,非常迷恋,并感到非常满足。
于是对老人说:“你给了我无穷的乐趣。为了奖赏你,你可以从我这儿得到你所要的任何东西”。
老人的要求是:请您在棋盘上的第一个格子上放1粒麦子,第二个格子上放2粒,第三个格子上放4粒,第四个格子上放8粒……即每一个次序在后的格子中放的麦粒都必须是前一个格子麦粒数目的倍数,直到最后一个格子放满为止。
国王哈哈大笑,慷慨地答应了老人这个卑微的请求。
然而,国王最终发现,按照与老人的约定,全国的麦子竟然连棋盘一小半格子数目都不够。
老人索要的麦粒数目实际上是天文数字,总数将是一个十九位数,折算重量约为2000多亿吨,即使现代,全球小麦的年产量也不过是数亿吨。

​我们要使用while循环作为主体,来帮助国王算一下,放满一个国际象棋棋盘,究竟需要多少粒麦子。

​同样,请先仔细进行思考,可以使用流程图或者伪代码的方式,有了比较清晰的思路再向下看。如果只是看看答案,缺少了思考,你很难真正掌握一门编程语言。

​看起来很长的一个问题,其实用程序解决起来无比的容易。当然对于初学者来讲,有一个清晰的思路比什么都重要。不然就好像看心灵鸡汤文,看了很多的道理,但仍然过不好这一生。

​这里介绍一种很常用的设计方法,叫做“快速原型法”。快速原型法其实并不是指什么特定的概念。其核心思想是,把需求先弄清楚,在整理需求的过程中,通过常识性的思考,快速的把已知的部分罗列出来,不能很快弄明白的可以先空着,直到最后,逐步将空白的部分填补完成,这时候形成的其实是伪代码。最后用程序代替所有的伪代码,形成最终的结果。
说起来容易,真正实践起来,还是需要很多的经验磨砺,才能得心应手。不过你先有一个概念就好,逐步的多读程序,多练习编写程序,就能做到。

​下面我们也尝试用这种方法来编写这个程序:

​1.理清需求。我们直接把需求写到程序的注释中:

"""
国际象棋有8行8列共64格,
第1个格子放1粒麦子,第2个格子放2粒麦子,
以后每格都比前面格子数量多一倍,
求麦子总数。
"""

​你看,那么长的文字,真正理顺弄清楚,真正对程序有影响的,并不多。

​2.想想在循环之前,我们都应当有什么初始的值要先考虑?

#定义一个变量来保存总的麦子数量,开始为0
c=0
#定义一个变量,循环1-64,来代表每一个格子
i=1
#假设每个格子中的麦子数量为x,初始也是1
x=1

​这一步就相当于循环的初始值,虽然只有i是用来控制循环,但既然我们用循环来帮助运算,那每一个跟循环有关的变量,不可能没有初始值。

​3.循环的过程部分,思考起来,似乎有点复杂,我们先想循环结束应当是什么?当然是显示结果:

#显示结果
print("64个格子,总的麦粒数量为:",c)

​4.虽然循环比较复杂,但就剩这一部分了,也不得不开始考虑。首先考虑循环的进入和退出条件。循环是从第一个格子,循环到第64个格子,因为包含第64个格子本身,所以循环条件肯定是<=64,如果是<64,那循环到第63个格子就结束了:

while i<=64:

​5.循环的边界条件定义好了,现在考虑真正的计算,也就是循环体的部分:

    c += x    #总数 累计上 这一个格子的麦粒数
    i += 1    #下一个格子
    x = x*2   #下一个格子的麦粒数是这一个格子的2倍

​其实总共就是这样三行不能再少的运算。注意这里我们都使用了变量自身赋值的简写形式。
还有一点就是我们前面的例子都是把i = i+1放到循环体最后,其实这并不是必须的,只要在循环体中修改了循环条件相关的变量,不会导致死循环就可以。在哪里为循环变量赋值,是程序人员根据方便程度决定的。

​好了,完整的贴一遍程序:

"""
国际象棋有8行8列共64格,
第1个格子放1粒麦子,第2个格子放2粒麦子,
以后每格都比前面格子数量多一倍,
求最终麦子总数。
"""

#定义一个变量来保存总的麦子数量,开始为0
c=0
#定义一个变量,循环1-64,来代表每一个格子
i=1
#假设每个格子中的麦子数量为x,初始也是1
x=1
#循环
while i<=64:
    c += x    #总数累计上这一个格子的麦粒数
    i += 1    #下一个格子
    x = x*2   #下一个格子的麦粒数是这一个格子的2倍
#显示结果
print("64个格子,总的麦粒数量为:",c)

​执行的结果一定要自己把程序输入进去之后,自己看一看。


练习时间

练习1:由用户输入一个整数n,用while循环求整数1直至n的和。(提示,上一讲介绍过函数input())

练习2:请将练习1的程序函数化,要求求和部分单独为一个函数。

练习3:请将棋盘麦粒问题函数化,以便求出1至指定格子的麦粒数量总和。因为过大的数字会超出Python的计算范围,我们假定允许用户输入的格子为1-64。


本讲小结

  • 计算机适合做枯燥、重复、大量的工作,循环在这种情况下起着重要的作用。while循环是较为自由的一种循环方式,用途很广泛
  • 循环的初始值和边界条件非常重要,让计算机执行正确,自己需要先设想自己处于计算机的位置上,想清楚
  • 循环的边界条件必须是可以变化的,需要循环的时候能循环,需要退出循环的时候要能变化条件,所以只能是变量
  • 判断边界条件,需要使用“比较运算符”
  • 比较运算符返回的是布尔值:True(真)、False(假),因为有了布尔值,计算机才能区别于计算器。特别注意区别比较运算的相等符号和赋值命令的等号

参考答案

中间的判断表达式及其结果:

逻辑判断表达式 结果
1 < 2 True
1 > 2 False
2.2 != 2.1 True
“a” > “b” False
“bcd” < “bd” True
a=”hello” 赋值表达式,不能当做逻辑条件使用
a == “hello” True
2 = 2 错误:2是字符,不是变量,不能被赋值

最后的三个练习请参考源码ex1.py/ex2.py/ex3.py