大家好,我是快快。今天为大家讲解几道Python应用在高中信息技术
中的经典题目。
“鸡兔同笼”最早记载于1500多年前的中国古代数学著作《孙子算经》中的“卷下”第31题(后传至日本演变为“鹤龟算”),原题为:“今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?”意思是“鸡和兔的总头数是35,总脚数是94,鸡和兔各有几只?”。
假设鸡有x只,兔有y只,根据题意列方程为:
x+y=35,2x+4y=94。
求解,得:x=23,y=12;即鸡有23只(46只脚)、兔有12只(48只脚)。
如果使用Python语言来编写程序的话,可使用for循环、range()函数和if条件判断来完成。
考虑到“鸡兔同笼”
原题中所给出的总头数和总脚数是固定的35和94,因此最终的求解也是固定的“23只鸡、12只兔”
。如果将题目进行“升级”
,鸡和兔的总头数与总脚数均由用户从键盘输入,仍然来求鸡和兔的数目,应该如何编写程序代码呢?
首先使用标准输入函数input来接收用户从键盘上输入的信息,比如“heads = input('请输入鸡和兔的总头数:')”
和“feet = input('请输入鸡和兔的总脚数:')”
。但在此需要特别注意的是,Python的input函数接收到的输入数据是str字符串(虽然表面上看是数字),必须要使用int来转换成整数型才能进行数学运算,语句为“heads = int(heads)”和“feet = int(feet)”
。
接下来仍然是使用range()函数进行for循环:“for x in range(0,(heads+1))”
。此时要充分考虑到用户所输入数据的计算结果,很有可能会出现“只有鸡”或“只有兔”的情况。举例:用户输入的总头数是10、总脚数是20,运算结果就应该是“10只鸡、0只兔”
;或输入总头数是10、总脚数是40,运算结果则是“0只鸡、10只兔”
。因为在计算机编程语言中,数字0总是被看作是最起始的值,Python的列表、字符串和元组等的元素均是从0开始进行索引的。不管是“0只鸡”
还是“0只兔”
,在计算机看来,这都是“鸡兔同笼”,只不过数目是0而已。另外,由于range()函数的两个参数是“左闭右开”型的区间,即第一个参数是被包括计算在内,而第二个参数却是不包括在内的(只计算到它的前一个元素);所以,第二个参数应该设置为“heads+1”,这样就能在循环时计算到它的前一个元素(即“heads”),也就是“0只兔”的情况(“x=0”则是“0只鸡”)。
循环体与之前类似,仍然是if条件判断“2*x + 4*y == feet”
是否成立,成立的话则使用print输出结果,然后使用break语句跳出循环。因为不确定用户从键盘上输入的两个数据是否恰好为“有效解”——鸡和兔的数目必须是整数只,所以在循环体外应该再添加一个“if 2*x + 4*y != feet”
判断语句,将这种无法进行整数结果计算的情况进行提示“输入的总头数和总脚数不合法”。没有该print语句的话,程序也能正常运行,但对于这种“意外”
没有任何提示,程序缺少必要的友好性。
最后将程序保存为“鸡兔同笼2.py”
,运行几次进行测试,输入的总头数和总脚数包括原题目中的“35、94”
、鸡兔各为0只、“30、110”
四种合法数值,程序均输出了正确的计算结果;最后一个测试输入“8、100”
,结果就提示“输入不合法”(如下图)。
Fibonacci(音译为:斐波那契
)数列又称“黄金分割数列”
,最早是由意大利数学家Fibonacci以兔子繁殖为例引入,因此又称“兔子数列”。其规则为:数列的第0项是0,第1项是第一个1,从第三项开始,每一项均等于前两项之和,即:0,1,1,2,3,5,8,13,21……
一般而言,兔子在出生两个月之后就会有繁殖能力,一对兔子每月能生出一对小兔子。假设兔子不死亡,一年之后会繁殖出多少对兔子?经分析后不难发现,成年兔子的对数符合这样的函数定义:
F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N)
如何使用Python编程来求解这样的Fibonacci数列呢?
“递归”
即函数在运行过程中不断地直接或间接调用自身的一种算法,比如在Python中通过“def fib1(n):”
来定义fib1()函数,其主体内容为“三分支”
结构:前两种(if和elif)通过判断参数n是0还是1来分别对应Fibonacci数列的前两项0和1,二者均通过return语句来返回对应的数值。注意判断条件中的双等号的含义是“等于”
,一个等号是“赋值”运算。第三个分支(else)是“return fib1(n-1)+fib1(n-2)”
,意思是递归运算返回该项前两项值的和:F(n)=F(n-1)+F(n-2)。
最后使用“print('一年之后会繁殖出的兔子对数为:',fib1(12))”
来输出运算结果,其中的“fib(12)”
作用是调用fib1()函数,参数为12(一年的月数)。保存程序为fibonacci1.py,运行后得到结果是144(如下图)。
Python支持多变量在一行语句中同时赋值的运算,比如“x,y=y,x”
,意思是x和y这两个变量的值进行“互换”。对于这种两个变量进行值互换的运算,其它编程语言几乎都是通过第三方变量来“暂存”中间数据的方式来完成的,例如最初有“x=3”和“y=4”
两个赋值语句,分别将3和4这两个数据给变量x和y;接着需要再通过三个赋值语句完成x和y数据的互换:“z=x”、“x=y”和“y=z”
,意思分别是`“将x的值(3)给z”、“将y的值(4)给x”和“将z的值(3)给y”,此时x的值变成4、y的值变成3。
如果使用Python的多变量同时赋值方法来编程,就可以通过“def fib2(n):”
来定义fib2()函数。首先通过“a,b = 0,1”
语句,实现变量a和b同时被分别赋值0和1,对应Fibonacci数列的前两项;接着使用for循环和range()函数“for i in range(n):”
,其循环体为“a,b = b,a+b”
,意思是将b的值给a、将a+b的值给b,实现之前使用递归算法完成的第三项及之后项的Fibonacci数列运算;for循环体结束后,通过“return a”语句将变量a的值返回;最后仍是通过print语句的“fib2(12)”
来调用函数计算并输出,保存程序为fibonacci2.py,运行结果仍是144(如下图)。
理论上讲,Fibonacci数列的值是无穷的,如何使用Python编程来实现输出Fibonacci数列任意项?仍然可以先通过input函数来接收用户从键盘上输入的“要求”
,注意一定要使用int()函数将该字符串型数据转换为整数型数据;接着定义fib3()函数,内容与上面的fib2()完全相同,同样是返回a的值;然后使用print语句输出提示信息,再同样是通过for循环加range()函数,循环体内的“print(fib3(i),end=' ')”
是调用fib3()函数,其中的“end=' '”
作用是控制打印输出的各项Fibonacci数列值之间使用一个空格来分隔(默认是回车)。将程序保存为fibonacci3.py,运行测试,分别尝试输入10、20和50,程序就会根据要求输出Fibonacci数列的前10、20和50个数值(如下图)。
印度有个古老传说:舍罕王打算奖赏国际象棋的发明人——西萨宰相,在被问及想要得到的赏赐时,宰相回答说:“在棋盘的第1格放1粒大米,第2格放2粒,第3格放4粒,之后的每一格中的米粒数目都是相邻前一格的两倍,一直放到最后的第64格,我只要这一棋盘的大米。”
最初国王不以为然,但最终的结果却是举全国之力都无法填满这个棋盘。果真是这样吗?我们使用Python编程来解决这个“棋盘米粒倍增”
问题。
首先通过“sum = 0”
语句建立并为变量sum赋值为0,准备存放最终的米粒数目;接着使用for循环:“for i in range(64):”
,其中的range()函数负责提供从0到63共64个循环计数;由于每格中米粒的数目可表示为“2的(n-1)次方”
,所以循环体语句为“sum += 2 ** i”
,将每次循环得到的该格子中米粒的数量与之前所有格子中米粒的数量和进行求和;循环结束后通过print语句将求和结果输出。
将程序保存为chessrice1.py,运行后得到结果(如下图):
棋盘米粒的总数为:18446744073709551615 粒。
Python的列表推导式在逻辑上等同于循环语句,优点是形式简洁且速度快,它能够以非常简洁的方式对列表(或其他可迭代对象)中的元素进行遍历、过滤或再次计算,从而快速生成满足特定需求的列表。
Python的列表推导式可分解为“表达式+循环”
两部分,比如通过“sum = sum([2**i for i in range(64)])”
这一个语句即可完成所有64格子中米粒的数量求和,其中的“2**i”即“表达式”
部分,作用是计算每格中的米粒数量;后面的“for i in range(64)”是“循环”
部分,作用是控制完成从0到63共64次循环;sum变量的赋值,是通过内置求和sum()函数来完成的。
之前使用常规循环求和法得到的结果是一个20位长的天文数字,单位是“粒”,不够直观。经查询,1千克大米约有52000粒,通过“mass = int(sum / 52000000)”
语句,将这些大米的数目转换成单位为“吨”并进行求整,赋值给mass变量,最后打印输出。将程序保存为chessrice2.py,运行后得到结果(如下图):
棋盘米粒的总数为:18446744073709551615 粒。
这些米粒的总质量为:354745078340 吨。
米粒总数的计算结果与循环求和法一致,它们的总质量是个12位数字,约是3547.5亿吨!当时,国王无论如何也拿不出数量如此庞大的大米,根本就填不满宰相的棋盘。
不管是使用常规循环求和还是使用列表推导式,我们都可以正确求解“棋盘米粒倍增”问题,二者在各种问题的求解过程中都比较方便,包括循环的嵌套,比如打印“九九乘法表”。
外层循环语句为“for i in range(1,10):”
,作用是从1到9循环;
内层循环语句为“for j in range(1,i+1):”
,同样是使用range()进行对应次数的循环;
循环体语句为“print('{0}*{1} = {2}'.format(j,i,i*j),end=' ') ”
,这个print语句用到了Python的format()方法进行字符串格式化,其中的“{0}”、“{1}”和“{2}”
是位置参数,作用是将后面“format(j,i,i*j)”
中的三个变量的对应数值进行占位输出;“end=' '”
的作用是设置末尾不换行,而不是print的默认“换行”值;内层循环结束后是一个“print()”
空语句,作用是换行,即打印完同一个乘数(比如同是乘以3)的一行循环后,回车换行。将程序保存为ninenine1.py,运行后得到“九九乘法表”(如下图)。
外层循环语句仍为“for i in range(1,10):”
,内层直接就是一个列表推导式(因为本身就是一层循环):“print(" ".join(["%d*%d=%-2d"%(j,i,j*i) for j in range(1,i+1)]))”
。这个print语句中的“join()”方法是将序列中的元素以指定的字符连接生成一个新字符串,依次连接到前面的" "空串后面;其中的“%d”
的作用是将数据按照整型格式化输出,“-”表示左对齐,“2”表示数字不足两位时进行位数补齐(不足位置用空格)。
列表推导式后面的循环部分是“for j in range(1,i+1)”
语句,与常规双层循环嵌套的内层循环语句完全相同。将程序保存为ninenine2.py,运行后,同样也得到了“九九乘法表”(如下图)。
假设要从10000个人中随机抽取出10人作为“中奖者”
,每人对应一个0-9999中的整数,要求使用Python编程按从小到大的顺序输出中奖者数字代号。类似的“随机抽奖”程序一般均需要先导入random模块,然后借助其中的randint()、shuffle()和sample()等函数进行随机数的选取;最后使用列表或集合对数据进行存储、排序和输出。
首先,通过“import random”导入random模块(下同);
接着,建立空列表“my_list1 = []”;建立while循环结构,判断条件为“len(my_list1) <= 10”
,即列表my_list1中元素的个数达到10为止(通过len()检测列表的长度);在循环体中,第一条语句为“x = random.randint(0,9999)”
,变量x取值为0-9999中的随机某个整数(包括0和9999);条件判断语句“if x not in my_list1”
的作用是,查看生成的随机数x是否在列表my_list1中,防止多次生成的随机数中有重复值出现;如果不重复,则使用append()方法将x追加到列表my_list1中:“my_list1.append(x)”
;当循环结束时,列表my_list1中就会保存有10个0-9999间的不重复数据。
最后,通过sorted()函数对列表my_list1进行默认参数排序(升序):“my_list2 = sorted(my_list1)”
,得到的列表my_list2就是从小到大顺序中奖号码,再使用print()输出结果即可。运行程序,得到了10个“中奖”号码(如图10)。
与法1类似,只不过是使用集合而非列表来存储生成的随机数:“my_set = set()”
,建立一个空集合;接着,仍然是在while循环中,通过randint生成0-9999间的某随机数,再将它追加到集合my_set中。由于集合中的元素是不可能存在重复数据的,因此不必像法1中的列表元素进行in成员运算判断,相当于直接进行了“去重”操作。循环结束后,仍然是使用sorted()函数进行排序并保存至列表my_list中,进行print打印输出(如下图)。
首先建立列表my_list1,其值为“list(range(10000))”
,通过list()将0至9999共10000个数据保存至列表my_list1中;接着使用random中的shuffle(),将列表my_list1中的数据进行随机排序:“random.shuffle(my_list1)”
;
然后对列表my_list1进行切片操作,任意截取出10个数据,比如“my_list1[:10]”是指从索引的第0个切至第9个(当然也可以使用“my_list2 = my_list1[99:109]”
,意思是从第99个切至第109个),将它们存入列表my_list2中;仍然是使用sorted()函数进行排序并保存至第3个列表my_list3中,进行print打印输出(如下图)。
Random中的sample()功能是从序列中随机多个“取样”
。首先建立列表my_list1,其值为从0-9999中随机抽取10个不重复的数据:“my_list1 = random.sample(range(10000),10)”
;然后就可以使用sorted()函数进行排序,将结果保存至列表my_list2中,最后进行print打印输出(如下图)。
Numpy中有个random.choice()
,可以随机从指定列表中提取若干个元素。
首先,通过“import numpy as np”导入numpy;
接着建立列表my_list1,存储的数据是0-9999共10000个数据:“my_list1 = list(range(10000))”
;建立列表my_list2,值为从列表my_list1中随机提取10个不重复的数据:“my_list2 = np.random.choice(my_list1,10,replace=False)”
,其中的参数“replace=False”即为控制随机数“不重复”。
最后,使用sorted()
函数进行排序并保存至第3个列表my_list3中,进行print打印输出即可(如下图)。
在编程语言的学习过程中,有一道经典的“水仙花数”
求解问题,即某个三位整数每个数位上数字的三次幂之和等于它本身,比如“153 = 1^3 + 5^3 + 3^3”
。其实,水仙花数只是“自幂数”的一种,类似的还有四位数的“四叶玫瑰数”、五位数的“五角星数”、六位数的“六合数”等等。
Python语法灵活,可以使用多种方法编程来完成自幂数的求解,在此略举几种水仙花数的编程方法:
在Python中,运算符“//”代表“整除”
运算,即求“整商”;而运算符“%”则是进行“求余”,利用这两种运算符可以将一个多位数的各位数字“分解”提取。
在判断一个三位数是否为水仙花数时,首先构建循环结构“for i in range(100,1000):”,百位上的数字提取方法是通过“bai_wei = i//100”求“整商”
来完成,比如计算“365//100”,结果就是“3”;十位上的数字提取方法是“shi_wei = (i%100)//10”,即先以100为除数进行“求余”,再将这个中间结果除以10求“整商”,比如计算“(365%100)//10”,会先得到余数65,然后计算“65//10”得到6;个位上的数字提取方法是“ge_wei = i%10”,即除以10求余数,比如“365%10”的结果是5。
循环中的if判断条件是“bai_wei3 + shi_wei3 + ge_wei**3 == i:”,即各数位上的数字的三次方之和与该数相等。最后,通过print打印输出变量i的数值,结果得到四个水仙花数:153、370、371和407(如下图)。
因为水仙花数是对一个三位数进行判断,所以直接构建三层循环嵌套来实现从100到999的顺序递增。最外层的“for bai_wei in range(1,10):”
控制百位数字循环,注意要从1开始(range()中的起始值和终止值参数为“左闭右开”区间);中间层的十位数字循环是“for shi_wei in range(0,10):”
;内部的个位数字循环是“for ge_wei in range(0,10):”,变量my_data是计算存储每个三位数的数值大小,即“bai_wei100+shi_wei10+ge_wei”;判断条件与之前相同,最后也是打印输出结果,同样会得到四个水仙花数:153、370、371和407(如下图)。
如果充分利用Python中的各种内置函数,比如map()
映射函数,可以非常巧妙地快速“提取”出每个多位数上各数位的数字。首先,同样是通过“for i in range(100,1000):”构建出循环结构;然后使用“序列解包”
的方式,同时为三个变量赋值——“bai_wei,shi_wei,ge_wei = map(int,str(i))”,借助map()函数将每个三位数先通过“str(i)”转换为字符串,再将int()函数映射至刚刚生成的字符串序列(迭代对象),就“还原”
得到了三个整形数字,分别赋值给三个对应的变量。
接下来仍是使用相同的判断语句和print()输出语句,同样会得到四个水仙花数:153、370、371和407(如下图 )。
众所周知,在Python中可构造“for i in range(100)”语句来执行100次循环,因为“range(100)”就相当于“range(0,100,1)”,是以1为步长、“左闭”(包括0)“右开”(不包括100)的;如果在该循环中被执行的语句是“print(i,end=' ')”的话,那就会打印输出从0、1、2……98、99共100个整数。按照这个规律,是否可以使用range()函数来生成类似的均匀浮点数呢?比如从0.00、0.01、0.02……0.98、0.99共100个浮点数。如果直接构造“for i in range(0,1,0.01)”,Python就会给出“TypeError: 'float' object cannot be interpreted as an integer”的错误提示,意思是“类型错误:浮点型对象不能解释为整数型”,因为range()函数接收的参数必须是整数(可以是负数),而不能直接处理float浮点数。那么,如何解决均匀浮点数生成问题呢?
首先建立并给变量i赋值为0.00;接着构造“while i <= 1.00:”循环,其中的第一条语句为“print('%.2f'%i,end=' ')”
,即以一个空格分隔并保留两位小数输出变量i的值;第二条语句为“i += 0.01”
,即控制i的自增,步长为0.01。运行程序,得到了从0.00到0.99共100个均匀浮点数(如下图)。
Python的列表推导式非常灵活,能够以非常简洁的方式来快速生成满足特定需求的列表。比如直接使用一条“my_list = [i/100 for i in range(100)]”
语句,即可在列表my_list中得到符合要求的100个浮点数,其实就是将“for i in range(100)”所得到的0-99分别进行了“i/100”的计算。最后再使用for循环以同样的方式来打印输出,同样也得到了100个均匀浮点数(如下图)。
numpy库中有个与Python的range()
函数功能类似的arange()
,它是支持浮点数运算的,而且同样是使用“初始值、终值、步长”
三个类似的参数进行调用。在使用“import numpy as np”语句以np为别名导入numpy库之后,再使用“my_list = list(np.arange(0,1,0.01))”语句,即可将arange()生成的ndarray数组对象转换为列表数据。最后,同样是使用for循环打印输出my_list中的所有元素,就得到了100个均匀浮点数(如下图)。
既然Python内置的range()函数不提供对浮点数的运算,那我们就可以自定义一个float_data()函数,三个参数依次为start、end和step,同样是对应“初始值、终值、步长”
。函数中使用变量i来接收初始值,然后通过while循环(当i<end时)中的“yield i”来向外返回i的值,当然还要有变量i的步长自增语句:“i += step”
。
在主程序中调用float_data()函数,接收到的数据存储至变量my_generator中,最后仍然是通过for循环来将它们打印输出,也可以得到100个均匀浮点数(如图22)。
四种方法均能实现均匀浮点数的生成,大家可根据自己的编程习惯来使用。当然,如果想生成的是0.000、0.001、0.002……0.999这样的千分位均匀浮点数,只要在程序中将步长修改为0.001、print输出“%.3f”
以及方法2中将“i/100”修改为“i/1000”等等即可。