精华内容
下载资源
问答
  • 网上有很多博客讲解遗传算法,但是大都只是“点到即止”,虽然给了一些代码实现,但也是“浅尝辄止”,没能很好地帮助大家进行扩展应用,抑或是进行深入的研究。 这是我的开篇之作~之前没有写博客的习惯,一般是将...

    加了个小目录~方便定位查看~

    前言

    正文

    一. 基础术语:

    二. 遗传算法基本算子:

    三.完整实现遗传算法:

    四.后记:


    前言

    网上有很多博客讲解遗传算法,但是大都只是“点到即止”,虽然给了一些代码实现,但也是“浅尝辄止”,没能很好地帮助大家进行扩展应用,抑或是进行深入的研究。

    这是我的开篇之作~之前没有写博客的习惯,一般是将笔记存本地,但久而久之发现回看不便,而且无法与大家交流和学习。现特此写下开篇之作,若有疏漏之处,敬请指正,谢谢!

    本文对遗传算法的原理进行梳理,相关代码是基于国内高校学生联合团队开源的高性能实用型进化算法工具箱geatpy来实现,部分教程引用了geatpy的官方文档:

    http://geatpy.com/index.php/details/

    geatpy官网:http://www.geatpy.com

    若有错误之处,恳请同志们指正和谅解,谢谢!

    因为是基于geatpy遗传和进化算法工具箱,所以下文的代码部分在执行前,需要安装geatpy:

    pip install geatpy

    安装时会自动根据系统版本匹配下载安装对应的版本。这里就有个小坑:如果最新版Geatpy没有与当前版本相匹配的包的话,会自动下载旧版的包。而旧版的包在Linux和Mac下均不可用。

    安装好后,在Python中输出版本检查是否是最新版(version 2.6.0):

    import geatpy as ea
    print(ea.__version__)
    

    下面切入主题:

    自然界生物在周而复始的繁衍中,基因的重组、变异等,使其不断具有新的性状,以适应复杂多变的环境,从而实现进化。遗传算法精简了这种复杂的遗传过程而抽象出一套数学模型,用较为简单的编码方式来表现复杂的现象,并通过简化的遗传过程来实现对复杂搜索空间的启发式搜索,最终能够在较大的概率下找到全局最优解,同时与生俱来地支持并行计算。

    下图展示了常规遗传算法(左侧) 和某种在并行计算下的遗传算法(右侧) 的流程。

    本文只研究经典的遗传算法,力求最后能够解决各种带约束单目标优化问题,并能够很轻松地进行扩展,让大家不仅学到算法理论,还能轻松地通过“复制粘贴”就能够将相关遗传算法代码结合到各类不同的现实问题的求解当中。

    从上面的遗传算法流程图可以直观地看出,遗传算法是有一套完整的“固定套路”的,我们可以把这个“套路”写成一个“算法模板”,即把:种群初始化、计算适应度、选择、重组、变异、生成新一代、记录并输出等等这些基本不需要怎么变的“套路”写在一个函数里面,而经常要变的部分:变量范围、遗传算法参数等写在这个函数外面,对于要求解的目标函数,由于在遗传进化的过程中需要进行调用目标函数进行计算,因此可以把目标函数、约束条件写在另一个函数里面。

    另外我们还可以发现,在遗传算法的“套路”里面,执行的“初始化种群”、“选择”、“重组”、“变异”等等,其实是一个一个的“算子”,在geatpy工具箱里,已经提供现行的多种多样的进化算子了,因此直接调用即可。

    Geatpy工具箱提供一个面向对象的进化算法框架,因此一个完整的遗传算法程序就可以写成这个样子:

    关于算法框架的详细介绍可参见:http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/

    下面就来详细讲一下相关的理论和代码实现:

    正文

    一. 基础术语:

        先介绍一下遗传算法的几个基础的术语,分别是:”个体“、”种群“、”编码与解码“、”目标函数值“、”适应度值“。

    1.个体:“个体”其实是一个抽象的概念,与之有关的术语有:

    (1)个体染色体:即对决策变量编码后得到的行向量。

    比如:有两个决策变量x1=1,x2=2,各自用3位的二进制串去表示的话,写成染色体就是:

    (2)个体表现型:即对个体染色体进行解码后,得到的直接指代各个控制变量的值的行向量。

     比如对上面的染色体“0 0 1 0 1 0”进行解码,得到 “1 2”,它就是个体的表现型,可看出该个体存储着两个变量,值分别是1和2。

    注意:遗传算法中可以进行“实值编码”,即可以不用二进制编码,直接用变量的实际值来作为染色体。这个时候,个体的染色体数值上是等于个体的表现型的。

    (3)染色体区域描述器:用于规定染色体每一位元素范围,详细描述见下文。

    2. 种群:“种群”也是一个抽象的概念,与之有关的术语有:

    (1)种群染色体矩阵(Chrom):它每一行对应一个个体的染色体。此时会发出疑问:一个个体可以有多条染色体吗?答:可以有,但一般没有必要,一条染色体就可以存储很多决策变量的信息了,如果要用到多条染色体,可以用两个种群来表示。

    例如:

    它表示有3个个体(因为有3行),因此有3条染色体。此时需要注意:这些染色体代表决策变量的什么值,我们是不知道的,我们也不知道该种群的染色体采用的是什么编码。染色体具体代表了什么,取决于我们采用什么方式去解码。假如我们采用的是二进制的解码方式,并约定上述的种群染色体矩阵中前3列代表第一个决策变量,后3列代表第二个决策变量,那么,该种群染色体就可以解码成:

    (2)种群表现型矩阵(Phen):它每一行对应一个个体的表现型。比如上图就是根据Chrom种群染色体矩阵解码得到的种群表现型矩阵。同样地,当种群染色体采用的是“实值编码”时,种群染色体矩阵与表现型矩阵实际上是一样的。

    (3)种群个体违反约束程度矩阵(CV):它每一行对应一个个体,每一列对应一种约束条件(可以是等式约束或不等式约束)。CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,大于0则表示不满足,且越大表示违反约束条件的程度越高。比如有两个约束条件:

    如何计算CV矩阵?可以创建两个列向量CV1和CV2,然后把它们左右拼合而成一个CV矩阵。

    假设x1、x2、x3均为存储着种群所有个体的决策变量值的列向量(这里可以利用种群表现型矩阵Phen得到,比如x1=Phen[:, [0]];x2=Phen[:, [1]]);x3=Phen[:, [2]]),这样就可以得到种群所有个体对应的x1、x2和x3)。

    那么:

    比如在某一代中,种群表现型矩阵Phen为:

    则有:

    此时CV矩阵的值为:

    由此可见,第一个个体满足两个约束条件;第二个个体违反了2个约束条件;第三和第四个个体满足第一个约束条件但违反了第二个约束条件。

    下面看下如何用代码来生成一个种群染色体矩阵:

    代码1. 实整数值种群染色体矩阵的创建:

    import numpy as np
    from geatpy import crtpc
    help(crtpc) # 查看帮助
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'RI' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“区域描述器”,表明有4个决策变量,范围分别是[-3.1, 4.2], [-2, 2],[0, 1],[3, 3],
    # FieldDR第三行[0,0,1,1]表示前两个决策变量是连续型的,后两个变量是离散型的
    FieldDR=np.array([[-3.1, -2, 0, 3],
                      [ 4.2,  2, 1, 5],
                      [ 0,    0, 1, 1]])
    # 调用crtri函数创建实数值种群
    Chrom=crtpc(Encoding, Nind, FieldDR)
    print(Chrom)
    

    代码1的运行结果:

    这里要插入讲一下“区域描述器”(见代码1中的FieldDR),它是用于描述种群染色体所表示的决策变量的一些信息,如变量范围、连续/离散性。另外还有一种区域描述器(FIeldD),用于描述二进制/格雷码的种群染色体。FieldDR和FieldD两个合称“Field”,又可以认为它们是“译码矩阵”。FieldDR具有以下的数据结构:

    代码1中的FieldDR矩阵的第三行即为这里的varTypes。它如果为0,表示对应的决策变量是连续型的变量;为1表示对应的是离散型变量。

    另一种则是用于二进制/格雷编码种群染色体解码的译码矩阵FieldD,它是具有以下结构的矩阵:

    其中,lens, lb, ub, codes, scales, lbin, ubin, varTypes均为长度等于决策变量个数的行向量。

    lens 包含染色体的每个子染色体的长度。sum(lens) 等于染色体长度。

    lb 和ub 分别代表每个决策变量的上界和下界。

    codes 指明染色体子串用的是二进制编码还是格雷编码。codes[i] = 0 表示第i 个变量使用的是标准二进制编码;codes[i] = 1 表示使用格雷编码。

    scales 指明每个子串用的是算术刻度还是对数刻度。scales[i] = 0 为算术刻度,scales[i] = 1 为对数刻度。对数刻度可以用于变量的范围较大而且不确定的情况,对于大范围的参数边界,对数刻度让搜索可用较少的位数,从而减少了遗传算法的计算量。(注意:当某个变量是对数刻度时,其取值范围中不能有0,即要么上下界都大于0,要么上下界都小于0。)

    从2.5.0版本开始,取消了对对数刻度的支持,该参数暂时保留,但不在起作用。

    lbin 和ubin 指明了变量是否包含其范围的边界。0 表示不包含边界;1 表示包含边界。

    varTypes 指明了决策变量的类型,元素为0 表示对应位置的决策变量是连续型变量;1 表示对应的是离散型变量。

    对于二进制编码,二进制种群的染色体具体代表决策变量的什么含义是不由染色体本身决定的,而是由解码方式决定的。因此在创建二进制种群染色体之初就要设定好译码矩阵(又称“区域描述器”)。

    因此,可以通过以下代码构建一个二进制种群染色体矩阵

    代码2. 二进制种群染色体矩阵的创建:

    import numpy as np
    from geatpy import crtpc
    help(crtpc) # 查看帮助
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'BG' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“译码矩阵”
    FieldD = np.array([[3, 2], # 各决策变量编码后所占二进制位数,此时染色体长度为3+2=5
                       [0, 0], # 各决策变量的范围下界
                       [7, 3], # 各决策变量的范围上界
                       [0, 0], # 各决策变量采用什么编码方式(0为二进制编码,1为格雷编码)
                       [0, 0], # 各决策变量是否采用对数刻度(0为采用算术刻度)
                       [1, 1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int))
                       [1, 1], # 各决策变量的范围是否包含上界(对bs2int实际无效)
                       [0, 0]])# 表示两个决策变量都是连续型变量(0为连续1为离散)
    # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵
    Chrom=crtpc(Encoding, Nind, FieldD)
    print(Chrom)
    

    代码2运行结果:

    3. 编码与解码

    对于实整数编码(即上面代码1所创建的实整数种群染色体),它是不需要解码,染色体直接就对应着它所代表的决策变量值。而对于代码2生成的二进制种群染色体矩阵,它需要根据译码矩阵FieldD来进行解码。在代码2后面添加以下语句即可解码:

    代码3(上接代码2):

    from geatpy import bs2ri
    help(bs2ri)
    Phen = bs2ri(Chrom, FieldD)
    print('表现型矩阵 = \n', Phen)

    运行效果如下:

    4.目标函数值

    种群的目标函数值存在一个矩阵里面(一般命名为ObjV),它每一行对应一个个体的目标函数值。对于单目标而言,这个目标函数值矩阵只有1列,而对于多目标而言,就有多列了,比如下面就是一个含两个目标的种群目标函数值矩阵:

    (这里Nind表示种群的规模,即种群含多少个个体;Nvar表示决策变量的个数)

    下面来跑一下代码,接着代码3,在对二进制染色体解码成整数值种群后,我们希望计算出f(x,y)=x+y这个目标函数值。同时设置一个等式约束:要求x + y = 3。于是完整代码如下:

    代码4:

    import numpy as np
    from geatpy import crtpc
    from geatpy import bs2ri
    
    def aim(Phen):
        x = Phen[:, [0]] # 取出种群表现型矩阵的第一列,得到所有个体的决策变量x
        y = Phen[:, [1]] # 取出种群表现型矩阵的第二列,得到所有个体的决策变量y
        CV = np.abs(x + y - 3) # 生成种群个体违反约束程度矩阵CV,以处理等式约束:x + y == 3
        f = x + y # 计算目标函数值
        return f, CV # 返回目标函数值矩阵
    
    # 定义种群规模(个体数目)
    Nind = 4
    Encoding = 'BG' # 表示采用“实整数编码”,即变量可以是连续的也可以是离散的
    # 创建“译码矩阵”
    FieldD = np.array([[3, 2], # 各决策变量编码后所占二进制位数,此时染色体长度为3+2=5
                       [0, 0], # 各决策变量的范围下界
                       [7, 3], # 各决策变量的范围上界
                       [0, 0], # 各决策变量采用什么编码方式(0为二进制编码,1为格雷编码)
                       [0, 0], # 各决策变量是否采用对数刻度(0为采用算术刻度)
                       [1, 1], # 各决策变量的范围是否包含下界(对bs2int实际无效,详见help(bs2int))
                       [1, 1], # 各决策变量的范围是否包含上界(对bs2int实际无效)
                       [0, 0]])# 表示两个决策变量都是连续型变量(0为连续1为离散)
    # 调用crtpc函数来根据编码方式和译码矩阵来创建种群染色体矩阵
    Chrom=crtpc(Encoding, Nind, FieldD)
    print('二进制染色体矩阵 = \n', Chrom)
    
    # 解码
    Phen = bs2ri(Chrom, FieldD)
    print('表现型矩阵 = \n', Phen)
    
    # 计算目标函数值矩阵
    ObjV, CV = aim(Phen)
    print('目标函数值矩阵 = \n', ObjV)
    print('CV矩阵 = \n', CV)
    

    运行结果如下:

    由上面对CV矩阵的描述可知,第三个个体的CV值为0,表示第三个个体满足x+y=3这个等式约束。其他都大于0,表示不满足该约束。

    疑问:CV矩阵有什么用呢?

    :CV矩阵既可用于标记非可行解,在含约束条件的优化问题中有用,又可用于度量种群个体违反各个约束条件的程度的高低。对于含约束条件的优化问题,我们可以采用罚函数或者是可行性法则来进行处理。罚函数法这里就不展开赘述了,最简单的罚函数可以是直接找到非可行解个体的索引,然后修改其对应的ObjV的目标函数值即可。

    对于可行性法则,它需要计算每个个体违反约束的程度,并把结果保存在种群类的CV矩阵中。CV矩阵的每一行对应一个个体、每一列对应一个约束条件(可以是等式约束也可以是不等式约束),CV矩阵中元素小于或等于0表示对应个体满足对应的约束条件,否则是违反对应的约束条件,大于0的值越大,表示违反约束的程度越高。生成CV标记之后,在后面调用适应度函数计算适应度时,只要把CV矩阵作为函数传入参数传进函数体,就会自动根据CV矩阵所描述的种群个体违反约束程度来计算出合适的种群个体适应度。

    5.适应度值

    适应度值通俗来说就是对种群个体的”生存能力的评价“。对于简单的单目标优化,我们可以简单地把目标函数值直接当作是适应度值(注意:当用geatpy遗传和进化算法工具箱时,则需要对目标函数值加个负号才能简单地把它当作适应度值,因为geatpy遵循的是”目标函数值越小,适应度值越大“的约定。)

    对于多目标优化,则需要根据“非支配排序”或是其他方法来确定种群个体的适应度值,本文不对其展开叙述。

    种群适应度(FitnV):它是一个列向量,每一行代表一个个体的适应度值:

    (这里Nind表示种群的规模,即种群含多少个个体)

    geatpy遗传和进化算法工具箱里面有几个函数可以计算种群个体的适应度 ,分别是:

    ranking、indexing、powing、scaling。具体的用法,可以用help命令查看,如help(ranking)。

    下面接着代码4,利用ranking(基于目标函数值排序的适应度分配)计算种群的适应度:

    代码5(接着代码4):

    from geatpy import ranking
    help(ranking)
    FitnV = ranking(ObjV, CV)
    print('种群适应度矩阵 = \n', FitnV)

    运行结果:

    分析这个结果我们发现,由于第1、2、4个体违反约束条件,而第三个个体满足约束条件,因此第3个个体的适应度最高。而在第1、2、4个体中,个体1的目标函数值最大,因此适应度最低。可见遵循“最小化目标”的约定,即目标函数值越小,适应度越大。

    好了,基本的术语和用法讲完后,下面讲一下遗传算法的基本算子:

     

    二. 遗传算法基本算子:

    我们不用破费时间去写底层的遗传算子,因为geatpy工具箱提供丰富的进化算子,以下所列算子不仅限于遗传算子:

     

     (如果是在iPython 控制台中调用可视化绘图函数(例如使用Spyder 开发工具),一般图像会默认显示在控制台或者是开发工具中。此时可以在iPython控制台下执行%matplotlib 来设置把图像显示在一个独立窗口中。)

    对于多目标优化,Geatpy中内置以下算子:

    可以用help(算子名)来查看对应的API文档,查看更详细的用法和例子。

    下面讲一下理论:

    1.选择:

    在进化算法中存在两个阶段的选择。第一次是参与进化操作的个体的选择。这个阶段的选择可以是基于个体适应度的、也可以是完全随机地选择交配个体。一旦个体被选中,那么它们就会参与交叉、变异等进化操作。未被选中的个体不会参与到进化操作中。

    第二次是常被称为“重插入”或“环境选择”的选择,它是指在个体经过交叉、变异等进化操作所形成的子代(或称“育种个体”)后用某种方法来保留到下一代从而形成新一代种群的过程。这个选择过程对应的是生物学中的” 自然选择”。它可以是显性地根据适应度(再次注意:适应度并不等价于目标函数值)来进行选择的,也可以是隐性地根据适应度(即不刻意去计算个体适应度)来选择。例如在多目标优化的NSGA-II 算法中,父代与子代合并后,处于帕累托分层中第一层级的个体以及处于临界层中的
    且拥挤距离最大的若干个个体被保留到下一代。这个过程就没有显性地去计算每个个体的适应度。

    经典的选择算子有:“轮盘赌选择”、“随机抽样选择”、“锦标赛选择”、“本地选择”、“截断选择”、“一对一生存者竞争选择”等等,这里不展开叙述了,可以参考:

    http://geatpy.com/index.php/2019/07/28/%E7%AC%AC%E5%9B%9B%E7%AB%A0%EF%BC%9A%E9%80%89%E6%8B%A9/

    这里要注意:遗传算法选择出的后代是可以有重复的。

    下面以低级选择函数:锦标赛选择算子(tour)为例,使用help(tour)查看其API,得到:

    实战演练如下:

    代码6:

    import numpy as np
    from geatpy import tour
    
    help(tour)
    FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
    chooseIdx = tour(FitnV, 6)
    print('个体的适应度为:\n', FitnV)
    print('选择出的个体的下标为:\n', chooseIdx)
    

    运行结果:

    光这样还不够,这里只是得出了选择个体的下标,如果我们需要得到被选中个体的染色体,同时尝试改用高级选择函数“selecting”来调用低级选择算子“tour”来进行选择,则可以如下操作:

    代码7:

    import numpy as np
    from geatpy import selecting
    
    help(selecting)
    Chrom=np.array([[1,11,21],
    [2,12,22],
    [3,13,23],
    [4,14,24],
    [5,15,25],
    [6,16,26],
    [7,17,27],
    [8,18,28]])
    
    FitnV = np.array([[1.2],[0.8],[2.1], [3.2],[0.6],[2.2],[1.7],[0.2]])
    SelCh = Chrom[selecting('tour', FitnV, 6), :] # 使用'tour'锦标赛选择算子,同时片取Chrom得到所选择个体的染色体
    print('个体的适应度为:\n', FitnV)
    print('选择后得到的种群染色矩阵为:\n', SelCh)
    

    代码7运行结果如下:

    将代码7中的'tour'换成工具箱中的其他选择算子的名称(如etour, rws, sus),就可以使用相应的选择算子进行选择。

    2.重组

    在很多的国内教材、博客文章、论文中,只提到“交叉”的概念。事实上,遗传算法的“交叉”是属于“重组”算子里面的。因为交叉算子经常使用,而对于“离散重组”、“中间重组”、“线性重组”等等,因为用的很少,所以我们常常只谈“交叉”算子了。交叉算子实际上是“值互换重组”(Values exchanged recombination)。这里就不展开叙述了,可以参考:

    http://geatpy.com/index.php/2019/07/28/%E7%AC%AC%E4%BA%94%E7%AB%A0%EF%BC%9A%E9%87%8D%E7%BB%84/

    与重组有关的遗传算法参数是“重组概率”(对于交叉而言就是“交叉概率”)(Pc),它是指两个个体的染色体之间发生重组的概率。

    下面以两点交叉(xovdp)为例,类似上面的“tour”那样我们使用help(xovdp)查看其API:

    代码实战演练如下:

    代码8:

    from geatpy import xovdp
    import numpy as np
    
    help(xovdp)
    OldChrom=np.array([[1,1,1,1,1],[1,1,1,1,1],[0,0,0,0,0],[0,0,0,0,0]]) #创建一个二进制种群染色体矩阵
    print('交叉前种群染色矩阵为:\n', OldChrom)
    NewChrom = xovdp(OldChrom, 1) # 设交叉概率为1
    print('交叉后种群染色矩阵为:\n', NewChrom)
    

    代码8运行结果如下:

    由此可见,xovdp是将前一半个体和后一半个体进行配对交叉的,有人认为应该随机选择交叉个体。事实上,在遗传算法进化过程中,各个位置的个体是什么,本身是随机的,不必要在交叉这里再增添一个随机(当然,可以在执行xovdp两点交叉之前,将种群染色体矩阵按行打乱顺序然后再交叉,从而满足自身需求)。

    3.变异

    下面以均匀突变(mutuni)为例,编写代码实现实数值编码的种群染色体的均匀突变,使用help(mutuni)查看API文档。

    代码9:

    from mutuni import mutuni
    import numpy as np
    # 自定义种群染色体矩阵,表示有3个个体,且染色体元素直接表示变量的值(即实值编码)
    OldChrom = np.array([[9,10],
                         [10,10],
                         [10,10]])
    # 创建区域描述器(又称译码矩阵)
    FieldDR = np.array([[7,8],
                        [10,10],
                        [1, 1]])
    # 此处设编码方式为实值编码中的“实整数编码”RI,表示染色体可代表实数和整数
    NewChrom = mutuni('RI', OldChrom, FieldDR, 1)
    print(NewChrom)
    

    代码9的运行结果如下:

    4.重插入

    经典遗传算法通过选择、重组和变异后,我们得到的是育种后代,此时育种后代的个体数有可能会跟父代种群的个体数不相同。这时,为了保持种群的规模,这些育种后代可以重新插入到父代中,替换父代种群的一部分个体,或者丢弃一部分育种个体,最终形成新一代种群。前面曾提到过“重插入”也是一种选择,但它是环境选择,是用于生成新一代种群的;而前面在交叉变异之前的选择是用于选择个体参与交叉变异,那个选择常被称作“抽样”。

    现考虑使用精英个体保留的遗传算法“EGA”,则重插入操作如下:

    代码10:

    from mutuni import mutuni
    import numpy as np
    # 自定义父代种群染色体(仅作为例子):
    Chrom = np.array([[1.1, 1.3],
                      [2.4, 1.2],
                      [3,   2.1],
                      [4,   3.1]])
    # 若父代个体的适应度为:
    FitnV = np.array([[1],
                     [2],
                     [3],
                     [4]])
    # 考虑采用“精英保留策略”的遗传算法,此时从父代选择出4-1=3个个体,经过交叉变异后假设子代的染色体为:
    offChrom = np.array([[2.1, 2.3],
                         [2.3, 2.2],
                         [3.4, 1.1]])
    # 假设直接把目标函数值当作适应度,且认为适应度越大越好。则通过以下代码重插入生成新一代种群:
    bestIdx = np.argmax(FitnV) # 得到父代精英个体的索引
    NewChrom = np.vstack([Chrom[bestIdx, :], offChrom]) # 得到新一代种群的染色体矩阵
    print('新一代种群的染色体矩阵为:\n', NewChrom)
    

    在“EGA”中,假设父代种群规模为N,则选择出(N-1)个个体进行交叉变异,然后找出父代的精英个体,用着个精英个体和交叉变异得到的子代个体进行“拼合”,得到新一代种群。

    除了这种重插入生成新一代种群的方法外,还有“父子两代个体合并选择”等更优秀的生成新一代种群的方法,这里就不一一赘述了。

    讲完上面的基本术语以及遗传算法基本算子后,我们就可以来利用遗传算法的“套路”编写一个遗传算法求解问题的程序了:

     

    三.完整实现遗传算法:

    上文提到遗传算法程序可以写成这个样子:

    其核心是算法模板类。在遗传算法模板里,我们根据遗传算法的“套路”,进行:初始化种群、目标函数值计算、适应度评价、选择、重组、变异、记录各代最优个体等操作。geatpy工具箱内置有开源的“套路模板”,源代码参见:

    https://github.com/geatpy-dev/geatpy/tree/master/geatpy/source-code/templets

    这些内置算法模板有详细的输入输出参数说明,以及遗传算法整个流程的完整注释,它们可以应对简单或复杂的、单目标优化的、多目标优化的、约束优化的、组合优化的等等的问题。

    但为了学习,我这里先不采用框架,直接利用工具箱提供的库函数来写一个带精英个体保留的遗传算法。这样代码量比较大,但有利于入门。

    编写代码 11、12,分别放在同一个文件夹下:

    代码11(目标函数aimfuc.py)(这里要回顾一下前面,Phen是种群表现型矩阵,存储的是种群所有个体的表现型,而不是单个个体。因而计算得到的目标函数值矩阵也是包含所有个体的目标函数值):

    # -*- coding: utf-8 -*-
    """
    aimfunc.py - 目标函数文件
    描述:
        目标:max f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
        约束条件:
            x1 != 10
            x2 != 5
            x1 ∈ [-3, 12.1] # 变量范围是写在遗传算法的参数设置里面
            x2 ∈ [4.1, 5.8]
    """
    
    import numpy as np
    
    def aimfunc(Phen, CV):
        x1 = Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值
        x2 = Phen[:, [1]]
        f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
        exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到
        exIdx2 = np.where(x2 == 5)[0]
        CV[exIdx1] = 1
        CV[exIdx2] = 1
        return [f, CV]
    

    然后是编写算法:

    代码12:

    # -*- coding: utf-8 -*-
    """main.py"""
    import numpy as np
    import geatpy as ea # 导入geatpy库
    from aimfunc import aimfunc # 导入自定义的目标函数
    import time
    
    """============================变量设置============================"""
    x1 = [-3, 12.1] # 第一个决策变量范围
    x2 = [4.1, 5.8] # 第二个决策变量范围
    b1 = [1, 1] # 第一个决策变量边界,1表示包含范围的边界,0表示不包含
    b2 = [1, 1] # 第二个决策变量边界,1表示包含范围的边界,0表示不包含
    ranges=np.vstack([x1, x2]).T # 生成自变量的范围矩阵,使得第一行为所有决策变量的下界,第二行为上界
    borders=np.vstack([b1, b2]).T # 生成自变量的边界矩阵
    varTypes = np.array([0, 0]) # 决策变量的类型,0表示连续,1表示离散
    """==========================染色体编码设置========================="""
    Encoding = 'BG' # 'BG'表示采用二进制/格雷编码
    codes = [0, 0] # 决策变量的编码方式,设置两个0表示两个决策变量均使用二进制编码
    precisions =[4, 4] # 决策变量的编码精度,表示二进制编码串解码后能表示的决策变量的精度可达到小数点后6位
    scales = [0, 0] # 0表示采用算术刻度,1表示采用对数刻度
    FieldD = ea.crtfld(Encoding,varTypes,ranges,borders,precisions,codes,scales) # 调用函数创建译码矩阵
    """=========================遗传算法参数设置========================"""
    NIND      = 100; # 种群个体数目
    MAXGEN    = 200; # 最大遗传代数
    maxormins = [-1] # 列表元素为1则表示对应的目标函数是最小化,元素为-1则表示对应的目标函数是最大化
    selectStyle = 'rws' # 采用轮盘赌选择
    recStyle  = 'xovdp' # 采用两点交叉
    mutStyle  = 'mutbin' # 采用二进制染色体的变异算子
    pc        = 0.7 # 交叉概率
    pm        = 1 # 整条染色体的变异概率(每一位的变异概率=pm/染色体长度)
    Lind = int(np.sum(FieldD[0, :])) # 计算染色体长度
    obj_trace = np.zeros((MAXGEN, 2)) # 定义目标函数值记录器
    var_trace = np.zeros((MAXGEN, Lind)) # 染色体记录器,记录历代最优个体的染色体
    """=========================开始遗传算法进化========================"""
    start_time = time.time() # 开始计时
    Chrom = ea.crtpc(Encoding, NIND, FieldD) # 生成种群染色体矩阵
    variable = ea.bs2ri(Chrom, FieldD) # 对初始种群进行解码
    CV = np.zeros((NIND, 1)) # 初始化一个CV矩阵(此时因为未确定个体是否满足约束条件,因此初始化元素为0,暂认为所有个体是可行解个体)
    ObjV, CV = aimfunc(variable, CV) # 计算初始种群个体的目标函数值
    FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值
    best_ind = np.argmax(FitnV) # 计算当代最优个体的序号
    # 开始进化
    for gen in range(MAXGEN):
        SelCh = Chrom[ea.selecting(selectStyle,FitnV,NIND-1),:] # 选择
        SelCh = ea.recombin(recStyle, SelCh, pc) # 重组
        SelCh = ea.mutate(mutStyle, Encoding, SelCh, pm) # 变异
        # 把父代精英个体与子代的染色体进行合并,得到新一代种群
        Chrom = np.vstack([Chrom[best_ind, :].astype(int), SelCh])
        Phen = ea.bs2ri(Chrom, FieldD) # 对种群进行解码(二进制转十进制)
        ObjV, CV = aimfunc(Phen, CV) # 求种群个体的目标函数值
        FitnV = ea.ranking(maxormins * ObjV, CV) # 根据目标函数大小分配适应度值
        # 记录
        best_ind = np.argmax(FitnV) # 计算当代最优个体的序号
        obj_trace[gen,0]=np.sum(ObjV)/ObjV.shape[0] #记录当代种群的目标函数均值
        obj_trace[gen,1]=ObjV[best_ind] #记录当代种群最优个体目标函数值
        var_trace[gen,:]=Chrom[best_ind,:] #记录当代种群最优个体的染色体
    # 进化完成
    end_time = time.time() # 结束计时
    ea.trcplot(obj_trace, [['种群个体平均目标函数值', '种群最优个体目标函数值']]) # 绘制图像
    """============================输出结果============================"""
    best_gen = np.argmax(obj_trace[:, [1]])
    print('最优解的目标函数值:', obj_trace[best_gen, 1])
    variable = ea.bs2ri(var_trace[[best_gen], :], FieldD) # 解码得到表现型(即对应的决策变量值)
    print('最优解的决策变量值为:')
    for i in range(variable.shape[1]):
        print('x'+str(i)+'=',variable[0, i])
    print('用时:', end_time - start_time, '秒')

    执行代码12得到如下结果:

    终于,我们把遗传算法完整地实现了,但扩展性还不够高。下面学习下如何使用Geatpy提供的进化算法框架来求解上述问题:(关于使用框架来优化的介绍可详见http://geatpy.com/index.php/geatpy%E6%95%99%E7%A8%8B/

    在这里我们可以回顾以下在本文开头提到的采用遗传算法的“套路”来编程求解问题的基本流程:

    其中执行脚本和问题类是需要编写的,算法模板类我直接调用Geatpy内置的"soea_EGA_templet"(带精英个体保留的单目标遗传算法模板)。下面开始编写代码:

    代码13:问题类"MyProblem"的编写:

    # -*- coding: utf-8 -*-
    """
    MyProblem.py
    该案例展示了一个简单的连续型决策变量最大化目标的单目标优化问题。
    max f = x * np.sin(10 * np.pi * x) + 2.0
    s.t.
    -1 <= x <= 2
    """
    
    import numpy as np
    import geatpy as ea
    
    class MyProblem(ea.Problem): # 继承Problem父类
        def __init__(self):
            name = 'MyProblem' # 初始化name(函数名称,可以随意设置)
            M = 1 # 初始化M(目标维数)
            maxormins = [-1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)
            Dim = 2 # 初始化Dim(决策变量维数)
            varTypes = [0] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的)
            lb = [-3, 4.1] # 决策变量下界
            ub = [12.1, 5.8] # 决策变量上界
            lbin = [1] * Dim # 决策变量下边界
            ubin = [1] * Dim # 决策变量上边界
            # 调用父类构造方法完成实例化
            ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
        
        def aimFunc(self, pop): # 目标函数
            x1 = pop.Phen[:, [0]] # 获取表现型矩阵的第一列,得到所有个体的x1的值
            x2 = pop.Phen[:, [1]]
            f = 21.5 + x1 * np.sin(4 * np.pi * x1) + x2 * np.sin(20 * np.pi * x2)
            exIdx1 = np.where(x1 == 10)[0] # 因为约束条件之一是x1不能为10,这里把x1等于10的个体找到
            exIdx2 = np.where(x2 == 5)[0]
            pop.CV = np.zeros((pop.sizes, 2))
            pop.CV[exIdx1, 0] = 1
            pop.CV[exIdx2, 1] = 1
            pop.ObjV = f # 计算目标函数值,赋值给pop种群对象的ObjV属性
        

    第二步:编写执行脚本“main.py”

    # -*- coding: utf-8 -*-
    """main.py"""
    import geatpy as ea # import geatpy
    from MyProblem import MyProblem # 导入自定义问题接口
    """===============================实例化问题对象================================"""
    problem = MyProblem() # 生成问题对象
    """==================================种群设置=================================="""
    Encoding = 'BG'       # 编码方式
    NIND = 100            # 种群规模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 创建区域描述器
    population = ea.Population(Encoding, Field, NIND) # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
    """================================算法参数设置================================="""
    myAlgorithm = ea.soea_EGA_templet(problem, population) # 实例化一个算法模板对象
    myAlgorithm.MAXGEN = 200 # 最大进化代数
    myAlgorithm.logTras = 1  # 设置每隔多少代记录日志,若设置成0则表示不记录日志
    myAlgorithm.verbose = True  # 设置是否打印输出日志信息
    myAlgorithm.drawing = 1  # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画)
    """===========================调用算法模板进行种群进化==============--==========="""
    [BestIndi, population] = myAlgorithm.run()  # 执行算法模板,得到最优个体以及最后一代种群
    BestIndi.save()  # 把最优个体的信息保存到文件中
    """==================================输出结果=================================="""
    print('用时:%f 秒' % myAlgorithm.passTime)
    print('评价次数:%d 次' % myAlgorithm.evalsNum)
    if BestIndi.sizes != 0:
        print('最优的目标函数值为:%s' % BestIndi.ObjV[0][0])
        print('最优的控制变量值为:')
        for i in range(BestIndi.Phen.shape[1]):
            print(BestIndi.Phen[0, i])
    else:
        print('没找到可行解。')

    运行"main.py"执行脚本即可得到以下结果:

                                                                                                         ......

    代码解析:在“main.py”执行脚本中,一开始需要实例化一个问题对象。然后是种群对象的实例化。在实例化种群对象前,需要设定种群的编码方式Encoding、种群规模NIND,并且生成区域描述器Field(或称译码矩阵),因为种群类的构造方法中需要至少用到这三个参数(详见“Population.py”中种群类的构造方法)。

    在完成了问题类对象和种群对象的实例化后,将其传入算法模板类的构造方法来实例化一个算法模板对象。这里我实例化的是单目标优化的EGA算法(即带精英个体保留的遗传算法)的模板类对象,即代码中的"soea_EGA_templet"。里面的进化算法具体是如何操作的,可详见https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/soeas/GA/EGA/soea_EGA_templet.py

    采用Geatpy提供的进化算法框架可以既能最大程度地描述清楚所要求解的问题,而且与进化算法是高度脱耦的,即上面在编写问题类的时候完全不需要管后面采用什么算法、采用什么样编码的种群,只需把问题描述清楚即可。

    而且,遗传算法有个好处是:目标函数可以写得相当复杂,可以解决各种复杂的问题,比如神经网络。以BP神经网络为例,可以把神经网络的参数作为决策变量,神经网络的训练误差作为目标函数值,只需把上面的例子修改一下就行了。

    而且,一般而言我们不需要像我刚刚最开始那样刻意去手写进化算法,可以直接调用geatpy内置的算法模板就可以解决问题了。geatpy工具箱提供这些内置的算法模板:

    应用Geatpy求解数学建模、工业设计、资源调度等实际优化问题的的朋友们可以直接使用这些算法模板快速解决各种灵活的优化问题。

    四.后记:

    最后十分感谢由Geatpy团队提供的高性能实用型遗传和进化算法工具箱,它提供开源的进化算法框架为遗传算法求解单目标/多目标优化、约束优化、组合优化等等给出了相当准确和快捷的解决方案。据称,geatpy的运行性能相当的高,远高于matlab的遗传算法工具箱、以及采用JAVA、matlab或者Python编写的一些进化优化平台或框架,比如jMetal、platemo、pymoo、deap等,后面有时间我将进行详细的性能对比实验分析,有相关经验的读者也可以自行对比性能。而且依我的体验来看,这是我网上到处找代码、找资料学习、碰了无数次壁后所看到的比较易学易用的工具箱了。

    最后值得注意的是Geatpy的顶层是面向对象的进化算法框架,底层是面向过程的进化算子。下面放一张geatpy的UML类图、算法调用的层次结构和库函数调用关系图,以此记录方便查看:

    下面附一张一位同行朋友使用犀牛软件(Rhinoceros)结合geatpy工具箱进行产品优化设计的截图:

    很多工程软件都提供Python接口,当需要用到进化优化时,就可以编写Python代码进行优化了。

    下一篇博客将介绍如何用遗传算法求解有向图的最短路径问题:

    https://blog.csdn.net/weixin_37790882/article/details/100622338

    后续我将继续学习和挖掘该工具箱的更多深入的用法。希望这篇文章在帮助自己记录学习点滴之余,也能帮助大家!

     

    展开全文
  • 下列Python语句的输出结果是 def f():pass print(type(f())) 结果: NoneType数据类型包含唯一值None,主要用于表示空值,如没有返回值的函数的结果 上机实践:2~6 2. 编写程序,输入本金、年利率和年数,计算复利...

    (还在更新中…) 这篇博客花费了我的大量时间和精力,从创作到维护;若认可本篇博客,希望给一个点赞、收藏

    并且,遇到了什么问题,请在评论区留言,我会及时回复的


    这本书对Python的知识点的描述很详细,而且排版看的很舒服

    1. 几个例题: 假装自己从零开始学,将一些有代表性、有意思的例题抽取出来
    2. 部分复习题: 遇到有意思的复习题,我会拿出来,并且进行分析
    3. 上机实践: 全部上机实践题的解题思路

    文章目录

    第一章 Python概述


    几个例题

    一:Python3.7.4下载

    python3.7.4下载地址:https://www.python.org/downloads/release/python-374/
    页面最下面:

    下载,安装完python后:出现的四个玩意:Python 3.7 Module Docs,IDLE,Python 3.7 Manuals,Python 3.7(64-bit)

    1. Python 3.7 Module Docs(64-bit)
      点击之后,会出现一个网页(将我下载的Python3.7.4文件夹中包含的模块都列了出来,页面不止这么点,还可以往下拉)

    2. IDLE(Python 3.7 64-bit)
      一个Python编辑器,Python内置的集成开发工具

    3. Python 3.7 Manuals(64-bit)
      Python 3.7 开发手册

    4. Python 3.7(64-bit)
      控制台中运行Python

    二:更新pip和setuptools包,安装NumPy包,安装Matplotlib包

    以下三个命令都是在控制台(windows中的cmd)中运行

    更新pip和setuptools包

    1. pip用于安装和管理Python扩展包
    2. setuptools用于发布Python包
    python -m pip install -U pip setuptools
    

    安装NumPy

    Python扩展模块NumPy提供了数组和矩阵处理,以及傅立叶变换等高效的数值处理功能

     python -m pip install NumPy
    

    安装Matplotlib包

    Matplotlib是Python最著名的绘图库之一,提供了一整套和MATLAB相似的命令API,既适合交互式地进行制图,也可以作为绘图控件方便地嵌入到GUI应用程序中

    python -m pip install Matplotlib
    

    三:使用IDLE打开和执行Python源文件程序

    首先:
    有一个.py文件test.py
    在这里插入图片描述

    使用IDLE打开.py文件的两种方式:

    1. 右键test.py---->Edit With IDLE---->Edit With IDLE 3.7(64-bit)
    2. 打开IDLE,然后File---->Open(或者ctrl+O)选择.py文件

    运行

    Run---->Run Module(或者F5
    就会出现这个界面,执行结果显示在这个界面中

    补充一点:
    如果在IDLE中编辑.py文件,记得修改后要保存(ctrl+s),再运行(F5

    四:使用资源管理器运行hello.py

    hello.py文件在桌面

    import random
    
    print("hello,Python")
    print("你今天的随机数字是:",random.choice(range(10)))#输出在0-9之间随机选择的整数
    input()
    
    1. 在桌面打开PowerShell(还有两种输入方式:python hello.py或者.\hello.py
    2. 或者在桌面打开cmd, 就输入hello.py或者python hello.py

    补充:上述两种命令中的hello.py都是相对路径,因为文件在桌面,而且我是在桌面打开cmd,所以文件路劲可以这么简简单单的写。如果文件存储位置和cmd打开位置不一样,请使用绝对路径

    五:命令行参数示例hello_argv.py

    hello_argv.py文件在桌面

    import sys
    
    print("Hello,",sys.argv[1])
    #这样写也行:
    #print("Hello,"+sys.argv[1])
    
    1. 在桌面打开PowerShell(还有两种输入方式:python hello_argv.py 任意输入或者./hello_argv.py 任意输入
    2. 或者在桌面打开cmd,就输入hello_argv.py 任意输入或者python hello_argv.py 任意输入

    补充:以图中第一个命令举例,hello_argv.pysys.argv[0]Pythonsys.argv[1]

    第二章 Python语言基础


    选择题:1、3、7、8

    1. 在Python中,以下标识符合法的是

    A. _B. 3CC. it’sB. str

    答案:A

    1. 标识符的第一个字符必须是字母,下划线(_);其后的字符可以是字母、下划线或数字。
    2. 一些特殊的名称,作为python语言的保留关键字,不能作为标识符
    3. 以双下划线开始和结束的名称通常具有特殊的含义。例如__init__为类的构造函数,一般应避免使用

    B:以数字开头,错误
    C:使用了',不是字母、下划线或数字
    D:str是保留关键字

    3. 在下列Python语句中非法的是

    A. x = y =1B. x = (y =1)C. x,y = y,xB. x=1;y=1

    答案:B,C

    7. 为了给整型变量x,y,z赋初值10,下面Python赋值语句正确的是

    A. xyz=10B. x=10 y=10 z=10C. x=y=z=10B. x=10,y=10,z=10

    答案:C

    1. 分号;用于在一行书写多个语句
    2. python支持链式赋值

    A:赋值对象是xyz
    B:分号;用于在一行书写多个语句,而不是' '(即空格)
    D:分号;用于在一行书写多个语句,而不是,

    8. 为了给整型变量x,y,z赋初值5,下面Python赋值语句正确的是

    A. x=5;y=5;z=5B. xyz=5C. x,y,z=10B. x=10,y=10,z=10

    答案:A

    Pytho能支持序列解包赋值,但是变量的个数必须与序列的元素个数一致,否则会报错

    B:赋值对象是xyz
    C:序列解包赋值,变量的个数必须与序列的元素个数一致,否则会报错
    D:分号;用于在一行书写多个语句,而不是,

    思考题:9

    9.下列Python语句的输出结果是

    def f():pass
    print(type(f()))
    

    结果:<class 'NoneType'>

    NoneType数据类型包含唯一值None,主要用于表示空值,如没有返回值的函数的结果

    上机实践:2~6

    2. 编写程序,输入本金、年利率和年数,计算复利(结果保留两位小数)

    money = int(input("请输入本金:"))
    rate = float(input("请输入年利率:"))
    years = int(input("请输入年数:"))
    amount = money*((1+rate/100)**years)
    print(str.format("本金利率和为:{0:2.2f}",amount))
    

    运行:

    请输入本金:1000
    请输入年利率:6.6
    请输入年数:10
    本金利率和为:1894.84
    

    3. 编写程序,输入球的半径,计算球的表面积和体积(结果保留两位小数)

    import math
    r = float(input("请输入球的半径:"))
    area = 4 * math.pi * r**2
    volume = 4/3*math.pi*r**3
    print(str.format("球的表面积为:{0:2.2f},体积为:{1:2.2f}",area,volume))
    

    运行:

    请输入球的半径:666
    球的表面积为:5573889.08,体积为:1237403376.70
    

    4. 编写程序,声明函数getValue(b,r,n),根据本金b,年利率r和年数n计算最终收益v

    money = int(input("请输入本金:"))
    rate = float(input("请输入年利率(<1):"))
    years = int(input("请输入年数:"))
    
    def getValue(b,r,n):
        return b*(1+r)**n
    
    print(str.format("本金利率和为:{0:2.2f}",getValue(money,rate,years)))
    

    运行:

    请输入本金:10000
    请输入年利率(<1):0.6
    请输入年数:6
    本金利率和为:167772.16
    

    5. 编写程序,求解一元二次方程x2-10x+16=0

    from math import sqrt 
    x = (10+sqrt(10*10-4*16))/2
    y = (10-sqrt(10*10-4*16))/2
    print(str.format("x*x-10*x+16=0的解为:{0:2.2f},{1:2.2f}",x,y))
    

    运行:

    x*x-10*x+16=0的解为:8.00,2.00
    

    6. 编写程序,提示输入姓名和出生年份,输出姓名和年龄

    import datetime
    sName = str(input("请输入您的姓名:"))
    birthday = int(input("请输入您的出生年份:"))
    age = datetime.date.today().year - birthday
    print("您好!{0}。您{1}岁。".format(sName,age))
    

    运行:

    请输入您的姓名:zgh
    请输入您的出生年份:1999
    您好!zgh。您20岁。
    

    案例研究:使用Pillow库处理图像文件

    https://blog.csdn.net/Zhangguohao666/article/details/102060722

    通过此案例,进一步了解Python的基本概念:模块、对象、方法和函数的使用

    第三章 程序流程控制


    几个例题

    一:编程判断某一年是否为闰年

    闰年:年份能被4整除但不能被100整除,或者可以被400整除。
    口诀:四年一闰,百年不闰,四百必闰

    代码一:

    y = int(input("请输入要判断的年份:"))
    if((y % 4 == 0 and y % 100 != 0) or y % 400 == 0):
        print("是闰年")
    else:
        print("不是闰年")
    

    代码二(使用calendar模块的isleap()函数来判断):

    from calendar import isleap
    
    y = int(input("请输入要判断的年份:"))
    if(isleap(y)):print("闰年")
    else:print("不是闰年")
    

    二:利用嵌套循环打印九九乘法表

    九九乘法表:

    for i in range(1,10):
        s = ""
        for j in range(1,10):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    下三角:

    for i in range(1,10):
        s = ""
        for j in range(1,i+1):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    上三角:

    for i in range(1,10):
        s = ""
        for k in range(1,i):
            s += "                   "
        for j in range(i,10):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    三:enumerate()函数和下标元素循环示例

    Python语言中的for循环直接迭代对象集合中的元素,如果需要在循环中使用索引下标访问集合元素,则可以使用内置的enumerate()函数

    enumerate()函数用于将一个可遍历的数据对象(例如列表、元组或字符串)组合为一个索引序列,并返回一个可迭代对象,故在for循环当中可直接迭代下标和元素

    seasons = ["Spring","Summer","Autumn","Winter"]
    for i,s in enumerate(seasons,start=1):    #start默认从0开始
        print("第{0}个季节:{1}".format(i,s))
    

    运行:

    第1个季节:Spring
    第2个季节:Summer
    第3个季节:Autumn
    第4个季节:Winter
    

    四:zip()函数和并行循环示例

    如果需要并行遍历多个可迭代对象,则可以使用Python的内置函数zip()

    zip()函数将多个可迭代对象中对应的元素打包成一个个元组,然后返回一个可迭代对象。如果元素的个数不一致,则返回列表的长度与最短的对象相同。

    利用运算符*还可以实现将元组解压为列表

    evens = [0,2,4,6,8]
    odds = [1,3,5,7,9]
    for e,o in zip(evens,odds):
        print("{0} * {1} = {2}".format(e,o,e*o))
    

    运行:

    0 * 1 = 0
    2 * 3 = 6
    4 * 5 = 20
    6 * 7 = 42
    8 * 9 = 72
    

    五:map()函数和循环示例

    如果需要遍历可迭代对象,并使用指定函数处理对应的元素,则可以使用Python的内置函数map()

    map(func,seq1[,seq2,...])
    
    • func作用于seq中的每一个元素,并将所有的调用结果作为可迭代对象返回。
    • 如果func为None,该函数的作用等同于zip()函数

    计算绝对值:

    >>> list(map(abs, [-1, 0, 7, -8]))
    [1, 0, 7, 8]
    

    计算乘幂:

    >>> list(map(pow, range(5), range(5)))
    [1, 1, 4, 27, 256]
    

    计算ASCII码:

    >>> list(map(ord, 'zgh'))
    [122, 103, 104]
    

    字符串拼接(使用了匿名函数lambda):

    >>> list(map(lambda x, y: x+y, 'zgh', '666'))
    ['z6', 'g6', 'h6']
    

    选择题:1、2、3

    1. 下面的Python循环体的执行次数与其他不同的是

    A.

    i = 0						
    while(i <= 10):
    	print(i)
    	i = i + 1
    

    B.

    i = 10
    while(i > 0):
    	print(i)
    	i = i - 1
    

    C.

    for i in range(10):
    	print(i)
    

    D.

    for i in range(10,0,-1):
    	print(i)
    

    答案:A

    A:[0,10] 执行11次
    B:[10,1] 执行10次
    C:[0,9) 执行10次
    D:[10,0) 执行10次

    2. 执行下列Python语句将产生的结果是

    x = 2; y = 2.0
    if(x == y): print("Equal")
    else: print("Not Equal")
    
    A. EqualB. Not EqualC. 编译错误D. 运行时错误

    答案:A

    Python中的自动类型转换:

    1. 自动类型转换注意针对Number数据类型来说的
    2. 当2个不同类型的数据进行运算的时候,默认向更高精度转换
    3. 数据类型精度从低到高:bool int float complex
    4. 关于bool类型的两个值:True 转化成整型是1;False 转化成整型是0

    int类型的2转化为float类型的2.0

    3. 执行下列Python语句将产生的结果是

    i= 1 	
    if(i): print(True) 	
    else: print(False)
    
    A. 输出1B. 输出TrueC. 输出FalseD. 编译错误

    答案:B

    在Python中,条件表达式最后被评价为bool值True或False。

    如果表达式的结果为数值类型(0),空字符串(""),空元组(()),空列表([]),空字典({}),其bool值为False,否则其bool值为True

    填空题:6

    6. 要使语句for i in range(_,-4,-2)循环执行15次,则循环变量i的初值应当为

    答案:26或者25

    一开始我给的答案是26,经过评论区 的提醒:
    在这里插入图片描述

    >>> a = 0
    >>> for i in range(26, -4, -2): a+=1
    
    >>> print(a)
    15
    
    >>> a = 0
    >>> for i in range(25, -4, -2): a+=1
    
    >>> print(a)
    15
    

    这种题目有一个规律:for i in range(x,y,z):
    若循环中没有break或者continue语句,
    执行次数的绝对值:result = (x-y)÷z

    但实际上没有这么简单:

    • 如果步长为 -1或者1,那么答案只有一个
    • 如果步长为 -2或者2,那么答案有两个
    • 如果步长为 -3或者3,那么答案有三个

    通过公式算出 x 之后,

    • 如果步长为2,还要计算 (x ± 1) - z × (result-1) 的值,然后再经过琐碎的判断即可
    • 如果步长为3,还要计算 (x ± 2) - z × (result-1) 的值,…

    虽然看着麻烦,但实际上是很好理解的

    思考题:3~6

    3. 阅读下面的Python程序,请问程序的功能是什么?

    from math import sqrt
    
    n = 0
    for m in range(101,201,2):
        k = int(sqrt(m))
        for i in range(2, k+2):
            if m % i == 0:break
        if i == k + 1:
            if n % 10 == 0:print()
            print('%d' % m,end = " ")
            n += 1
    

    输出101到200之间的素数
    每行输出10个,多余换行

    运行:

    101 103 107 109 113 127 131 137 139 149 
    151 157 163 167 173 179 181 191 193 197 
    199
    

    素数(质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。

    4. 阅读下面的Python程序,请问输出的结果使什么?

    n = int(input("请输入图形的行数:"))
    for i in range(0, n):
        for j in range(0, 10 - i):print(" ", end=" ")
        for k in range(0, 2 * i + 1):print(" * ", end=" ")
        print("\n")
    

    输出的是一个金字塔

    运行:

    请输入图形的行数:4
                         *  
    
                       *   *   *  
    
                     *   *   *   *   *  
    
                   *   *   *   *   *   *   *  
    

    5. 阅读下面的Python程序,请问输出的结果使什么?程序的功能是什么?

    for i in range(100,1000):
        n1 = i // 100
        n2 = i // 10 % 10
        n3 = i % 10
        if(pow(n1, 3) + pow(n2, 3) + pow(n3, 3) == i):print(i, end=" ")
    

    输出三位数中所有的水仙花数

    运行:

    153 370 371 407 
    

    水仙花数 是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身

    6. 阅读下面的Python程序,请问输出的结果使什么?程序的功能是什么?

    for n in range(1,1001):
        total = 0; factors = []
        for i in range(1, n):
            if(n % i == 0):
                factors.append(i)
                total += i
        if(total == n):print("{0} : {1}".format(n, factors))    
    

    输出1到1000的所有完数,并输出每个完数的所有因子

    运行:

    6 : [1, 2, 3]
    28 : [1, 2, 4, 7, 14]
    496 : [1, 2, 4, 8, 16, 31, 62, 124, 248]
    

    完数 所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身

    上机实践:2~14

    2. 编写程序,计算1=2+3+…+100之和

    1. 使用for循环(递增):
    total = 0
    for i in range(101):
        total += i
    print(total) 
    
    1. 使用求和公式:
    >>> (1 + 100) * 100 /2
    5050.0
    
    1. 使用累计迭代器itertools.accumulate
    >>> import itertools
    >>> list(itertools.accumulate(range(1, 101)))[99]
    5050
    

    3. 编写程序,计算10+9+8+…+1之和

    1. 使用for循环(递增):
    total = 0
    for i in range(11):
        total += i
    print(total) 
    
    1. 使用for循环(递减):
    total = 0
    for i in range(10,0,-1):
        total += i
    print(total)   
    
    1. 使用求和公式:
    >>> (1 + 10) * 10 / 2
    55.0
    
    1. 使用累计迭代器itertools.accumulate
    >>> import itertools
    >>> list(itertools.accumulate(range(1,11)))[9]
    55
    

    4. 编写程序,计算1+3+5+7+…+99之和

    1. 使用for循环(递增):
    total = 0
    for i in range(1,100,2):
        total += i
    print(total)     
    
    1. 使用求和公式:
    >>> (1 + 99) * 50 /2
    2500.0
    
    1. 使用累计迭代器itertools.accumulate
    >>> import itertools
    >>> list(itertools.accumulate(range(1,100,2)))[49]
    2500
    

    5. 编写程序,计算2+4+6+8+…+100之和

    1. 使用for循环(递增):
    total = 0
    for i in range(2,101,2):
        total += i
    print(total)     
    
    1. 使用求和公式:
    >>> (2 + 100) * 50 / 2
    2550.0
    
    1. 使用累计迭代器itertools.accumulate
    >>> import itertools
    >>> x = list(itertools.accumulate(range(2,101,2)))
    >>> x[len(x)-1]
    2550
    

    6. 编写程序,使用不同的实现方法输出2000~3000的所有闰年

    代码一:

    for y in range(2000,3001):
        if((y % 4 == 0 and y % 100 != 0) or y % 400 == 0):
            print(y,end = ' ')
    

    代码二(使用calendar模块的isleap()函数来判断):

    from calendar import isleap
    
    for y in range(2000,3001):
        if(isleap(y)):print(y,end = " ")
    

    运行:

    2000 2004 2008 2012 2016 2020 2024 2028 2032 2036 2040 2044 2048 2052 2056 2060 2064 2068 2072 2076 2080 2084 2088 2092 2096 2104 2108 2112 2116 2120 2124 2128 2132 2136 2140 2144 2148 2152 2156 2160 2164 2168 2172 2176 2180 2184 2188 2192 2196 2204 2208 2212 2216 2220 2224 2228 2232 2236 2240 2244 2248 2252 2256 2260 2264 2268 2272 2276 2280 2284 2288 2292 2296 2304 2308 2312 2316 2320 2324 2328 2332 2336 2340 2344 2348 2352 2356 2360 2364 2368 2372 2376 2380 2384 2388 2392 2396 2400 2404 2408 2412 2416 2420 2424 2428 2432 2436 2440 2444 2448 2452 2456 2460 2464 2468 2472 2476 2480 2484 2488 2492 2496 2504 2508 2512 2516 2520 2524 2528 2532 2536 2540 2544 2548 2552 2556 2560 2564 2568 2572 2576 2580 2584 2588 2592 2596 2604 2608 2612 2616 2620 2624 2628 2632 2636 2640 2644 2648 2652 2656 2660 2664 2668 2672 2676 2680 2684 2688 2692 2696 2704 2708 2712 2716 2720 2724 2728 2732 2736 2740 2744 2748 2752 2756 2760 2764 2768 2772 2776 2780 2784 2788 2792 2796 2800 2804 2808 2812 2816 2820 2824 2828 2832 2836 2840 2844 2848 2852 2856 2860 2864 2868 2872 2876 2880 2884 2888 2892 2896 2904 2908 2912 2916 2920 2924 2928 2932 2936 2940 2944 2948 2952 2956 2960 2964 2968 2972 2976 2980 2984 2988 2992 2996 
    

    7. 编写程序,计算Sn=1-3+5-7+9-11…

    代码一:

    n = int(input("项数:"))
    total = 0
    flag = True
    for i in range(1,2*n,2):
        if(flag):
            total += i
            flag = False
        else:
            total -= i
            flag = True
    print(total)
    

    代码二:

    n = int(input("项数:"))
    total = 0
    x = 2
    for i in range(1,2*n,2):
        total += pow(-1,x)*i
        x += 1 
    print(total)
    

    运行:

    项数:10
    -10
    

    8. 编写程序,计算Sn=1+1/2+1/3+…

    n = int(input("项数:"))
    total = 0.0
    for i in range(1,n+1):
        total += 1/i 
    print(total)
    

    运行:

    项数:10
    2.9289682539682538
    

    9. 编写程序,打印九九乘法表。要求输入九九乘法表的各种显示效果(上三角,下三角,矩形块等方式)

    矩形块:

    for i in range(1,10):
        s = ""
        for j in range(1,10):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    下三角:

    for i in range(1,10):
        s = ""
        for j in range(1,i+1):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    上三角:

    for i in range(1,10):
        s = ""
        for k in range(1,i):
            s += "                   "
        for j in range(i,10):
            s += str.format("%d * %d = %02d  " %(i, j, i*j))
        print(s)
    

    10. 编写程序,输入三角形的三条边,先判断是否可以构成三角形,如果可以,则进一步求三角形的周长和面积,否则报错“无法构成三角形!”

    from math import sqrt
    
    a = float(input("请输入三角形的边长a:"))
    b = float(input("请输入三角形的边长b:"))
    c = float(input("请输入三角形的边长c:"))
    
    if(a < b): a,b = b,a
    if(a < c): a,c = c,a
    if(b < c): b,c = c,b
    
    if(a < 0 or b < 0 or c < 0 or b+c <= a): print("无法构成三角形!")
    else:
        h = (a+b+c)/2
        area = sqrt(h*(h-a)*(h-b)*(h-c))
        print("周长:{0},面积:{1}".format(a+b+c,area))
    

    运行:

    请输入三角形的边长a:4
    请输入三角形的边长b:3
    请输入三角形的边长c:5
    周长:12.0,面积:6.0
    

    11. 编写程序,输入x,根据如下公式计算分段函数y的值。请分别用单分支语句,双分支语句结构以及条件运算语句等方法实现

    y = (x2-3x)/(x+1) + 2π + sinx (x≥0 )
    y = ln(-5x) + 6√(|x|+e4) - (x+1)3 (x<0)

    单分支语句:

    import math
    
    x = float(input("请输入x:"))
    if(x >= 0):
        y = (x*x - 3*x)/(x+1) + 2*math.pi + math.sin(x)
    if(x < 0):
        y = math.log(-5*x) + 6 * math.sqrt(abs(x) + math.exp(4)) - pow(x+1,3)
    
    print(y)
    
    
    

    双分支语句:

    import math
    
    x = float(input("请输入x:"))
    if(x >= 0):
        y = (x*x - 3*x)/(x+1) + 2*math.pi + math.sin(x)
    else:
        y = math.log(-5*x) + 6 * math.sqrt(abs(x) + math.exp(4)) - pow(x+1,3)
    
    print(y)
    

    条件运算语句:

    import math
    
    x = float(input("请输入x:"))
    y = ((x*x - 3*x)/(x+1) + 2*math.pi + math.sin(x)) if(x >= 0) \
    else (math.log(-5*x) + 6 * math.sqrt(abs(x) + math.exp(4)) - pow(x+1,3)) 
    
    print(y)
    

    运行一:

    请输入x:666
    668.2715406628656
    

    运行二:

    请输入x:-666
    294079794.1744833
    

    12. 编写程序,输入一元二次方程的3个系数a、b、c,求ax2+bx+c=0方程的解

    import math
    
    a = float(input("请输入系数a:"))
    b = float(input("请输入系数b:"))
    c = float(input("请输入系数c:"))
    
    delta = b*b -4*a*c
    
    if(a == 0):
        if(b == 0): print("无解")
        else: print("有一个实根:",-1*c/b)
    elif(delta == 0): print("有两个相等实根:x1 = x2 = ", (-1*b)/(2*a))
    elif(delta > 0): print("有两个不等实根:x1 = {0},x2 = {1}".format\
                           ((-1*b +math.sqrt(delta))/2*a,(-1*b -math.sqrt(delta))/2*a))
    elif(delta < 0): print("有两个共轭复根:x1 = {0},x2 = {1}".format\
                           (complex( (-1*b)/(2*a),math.sqrt(delta*-1)/(2*a)),complex( (-1*b)/(2*a),-1*math.sqrt(delta*-1)/(2*a))))
    

    运行一:

    请输入系数a:0
    请输入系数b:0
    请输入系数c:10
    无解
    

    运行二:

    请输入系数a:0
    请输入系数b:10
    请输入系数c:5
    有一个实根: -0.5
    

    运行三:

    请输入系数a:1
    请输入系数b:8
    请输入系数c:16
    有两个相等实根:x1 = x2 =  -4.0
    

    运行四:

    请输入系数a:1
    请输入系数b:-5
    请输入系数c:6
    有两个不等实根:x1 = 3.0,x2 = 2.0
    

    运行五:

    请输入系数a:5
    请输入系数b:2
    请输入系数c:1
    有两个共轭复根:x1 = (-0.2+0.4j),x2 = (-0.2-0.4j)
    

    13. 编写程序,输入整数n(n≥0),分别利用for循环和while循环求n!

    1. for循环
    n = int(input("请输入n:"))
    
    if(n == 0): total = 1
    if(n > 0):
        total = 1
        for i in range(n,0,-1):
            total *= i
    
    print(total)
    
    
    1. while循环
    n = int(input("请输入n:"))
    
    if(n == 0): total = 1
    if(n > 0):
        total = 1
        while(n >= 1):
            total *= n
            n -= 1
    
    print(total)
    
    1. 补充一个:使用累计迭代器itertools.accumulate
    >>> import itertools, operator
    >>> n = int(input('请输入n:'))
    请输入n:7
    >>> x = list(accumulate(range(1, n+1), operator.mul))
    >>> x[len(x)-1]
    5040
    

    14. 编写程序,产生两个0~100(包含0和100)的随机整数a和b,求这两个整数的最大公约数和最小公倍数

    1. 现有知识点解决方法
    
    import random
    
    a = random.randint(0,100)
    b = random.randint(0,100)
    sum = a*b
    
    print(a) #输出原来的a,b
    print(b)
    
    if(a < b): a,b = b,a
    
    while(a%b != 0):
        a,b = b,a%b
    
    print("最大公约数:{0},最小公倍数:{1}".format(b,sum/b))
    
    
    1. 补充:使用生成器(generate)函数:yield
    >>> def func(a, b):
    	if(a < b): a,b = b,a
    	while(a%b != 0):
    		a,b = b,a%b
    		yield b
    
    		
    >>> import random
    >>> if __name__ == '__main__':
    	a = random.randint(0,100)
    	b = random.randint(0,100)
    	sum = a*b
    	print(a,b)
    	t = list(iter(func(a, b)))
    	gcd = t[len(t)-1]
    	print("gcd = {0}, mcm = {1}".format(gcd, sum/gcd))
    
    	
    29 65
    gcd = 1, mcm = 1885.0
    
    1. 补充:使用math模块中的gcd(x,y)函数
    >>> import random
    >>> import math
    >>> if __name__ == '__main__':
    	a = random.randint(0,100)
    	b = random.randint(0,100)
    	sum = a*b
    	print(a,b)
    	gcd = math.gcd(a,b)
    	print("gcd = {0}, mcm = {1}".format(gcd, sum/gcd))
    
    	
    29 48
    gcd = 1, mcm = 1392.0
    

    案例研究:使用嵌套循环实现图像处理算法

    https://blog.csdn.net/Zhangguohao666/article/details/103935185

    通过图像处理算法案例,深入了解Python数据结构和基本算法流程

    第四章 常用内置数据类型


    几个例题

    一:Python内置数据类型概述

    Python中一切皆为对象,而每个对象属于某个数据类型

    Python的数据类型包括:

    1. 内置的数据类型
    2. 模块中定义的数据类型
    3. 用户自定义的类型

    四种内置的数值类型:int,float,bool,complex

    1. int
      与其他计算机语言有精度限制不同,Python中的整数位数可以为任意长度(只受限于计算机内存)。
      整型对象是不可变对象。
    2. float
      与其他计算机语言中的double和float对应
      Python的浮点类型的精度和系统相关
    3. bool
    4. complex
      当数值字符串中包含虚部j(或J)时即复数字面量

    序列数据类型:str,tuple,bytes,list,bytearray

    序列数据类型表示若干有序数据.

    不可变序列数据类型:

    1. str(字符串)
      表示Unicode字符序列,例如:“zgh666”
      在Python中没有独立的字符数据类型,字符即长度为1的字符串
    2. tuple(元组)
      表示任意数据类型的序列,例如:(“z”,“g”,“h”,6,6,6)
    3. bytes(字节序列)
      表示字节(8位)序列数据

    可变序列数据类型:

    1. list(列表)
      表示可以修改的任意类型数据的序列,比如:[‘z’,‘g’,‘h’,6,6,6]
    2. bytearray(字节数组)
      表示可以修改的字节(8位)数组

    集合数据类型:set,frozenset

    集合数据类型表示若干数据的集合,数据项目没有顺序,且不重复

    1. set(集)
      例如:{1,2,3}
    2. frozenset(不可变集)

    字典数据类型:dict

    字典数据类型用于表示键值对的字典
    例如:{1:"zgh", 2:666}

    NoneType,NotImplementedType,EllipsisType

    1. NoneType数据类型包含唯一值None,主要用于表示空值,如没有返回值的函数的结果
    2. NotImplementedType数据类型包含唯一值NotImplemented,在进行数值运算和比较运算时,如果对象不支持,则可能返回该值
    3. EllipsisType数据类型包含唯一值Ellipsis,表示省略字符串符号...

    其他数据类型

    Python中一切对象都有一个数据类型,模块、类、对象、函数都属于某种数据类型
    Python解释器包含内置类型,
    例如:
    代码对象Code objects
    框架对象Frame objects
    跟踪对象Traceback objects
    切片对象Slice objects
    静态方法对象Static method objects
    类方法对象Class method objects

    二:整型字面量示例

    Python3.7支持使用下划线作为整数或者浮点数的千分位标记,以增强大数值的可阅读性。
    二进制、八进制、十六进制则使用下划线区分4位标记

    1_000_000_000  #输出1000000000
    
    0xff_ff_ff_ff  #输出4294967295
    0x_FF_FF_FF_FF  #输出4294967295
    

    三:字符串字面量示例

    两个紧邻的字符串,如果中间只有空格分隔,则自动拼接位一个字符串

    'zgh' '666'  #输出'zgh666'
    'zgh' + "666"   #输出'zgh666'
    

    四:转义字符示例

    转义字符后跟Unicode编码也可以表示字符

    1. \ooo八进制Unicode码对应的字符
    2. \xhh十六进制Unicode码对应的字符
    '\101'  #输出'A'
    '\x41'  #输出'A'
    

    使用r’‘或者R’'的字符串称为原始字符串,其中包含的任何字符都不进行转义

    s = r'换\t行\t符\n'
    s  		  #输出:'换\\t行\\t符\\n'
    print(s)  #输出:换\\t行\\t符\\n
    

    五:字符串的格式化

    一:

    "student number:{0},score_average:{1}".format(2,100)
    #输出:'student number:2,score_average:100'
    

    二:

    str.format("student number:{0},score_average:{1}",2,100)
    #输出:'student number:2,score_average:100'
    

    三(兼容Python2的格式,不推荐使用):

     "student number:%4d,score_average:%2.1f" %(2,100)
     #输出:'student number:   2,score_average:100.0'
    

    六:字符串示例,格式化输出字符串堆积的三角形

    1. str.center()方法用于字符串两边填充
    2. str.rjust()方法用于字符串右填充
    print("1".center(20))		#一行20个字符,居中对齐
    print(format("121","^20"))	#一行20个字符,居中对齐
    print("1".rjust(20,"*"))	#一行20个字符,右对齐,加*
    print(format("121","*>20"))	#一行20个字符,右对齐,加*
    

    运行:

             1          
            121         
    *******************1
    *****************121
    

    选择题:11

    11. 关于Python字符串,下列说法错误的是

    A. 字符即长度为1的字符串
    B. 字符串以/0标识字符串的结束
    C. 用户既可以用单引号,也可以用双引号创建字符串
    D. 用三引号字符串中可以包含换行回车等特殊字符

    答案:B

    Python中字符串不是用\0来判断字符串结尾,
    每个字符串都存有字符串的长度,通过计数来判断是否到达结尾。

    虽然在c语言中\0就是来判断字符串的结尾;

    填空题:4、7、8、9、10、13、21

    4. Python表达式3 ** 2 ** 3的值为

    答案:6561

    表达式中,相同优先级的运算,从右往左

    7. Python语句print(pow(-3,2),round(18.67,1),round(18.67,-1))的输出结果是

    答案:9 18.7 20.0

    pow()幂运算
    round()四舍六入,五留双

    8. Python语句print(round(123.84,0),round(123.84,-2),floor(15.5))的输出结果是

    答案:124.0 100.0 15

    补充:floor()是math模块中的方法,向下取整

    9. Python语句print(int(‘20’,16),int(‘101’,2))的输出结果是

    答案:32 5

    注意:int(x,y)是指将y进制的数值x转化为10进制数

    10. Python语句print(hex(16),bin(10))的输出结果是

    答案:0x10 0b1010

    hex(x)将十进制数x转化为十六进制,以字符串形式输出
    bin(x)将十进制数x转化为二进制,以字符串形式输出

    13. Python语句print(gcd(12,16),divmod(7,3))的输出结果是

    答案:4 (2,1)

    gcd()是math模块中的函数,求最大公约数
    divmod()是内置函数,返回商和余数

    21. Python语句序列 x=True;y=False;z=False;print(x or y and z) 的运行结果是

    答案:True

    and优先级比or高

    思考题:5

    5. 阅读下面的Python程序,请问输出结果是什么?

    from decimal import *
    
    ctx = getcontext()
    ctx.prec = 2
    print(Decimal('1.78'))#1.78
    print(Decimal('1.78') + 0)#1.8
    ctx.rounding = ROUND_UP
    print(Decimal('1.65') + 0)#1.7
    print(Decimal('1.62') + 0)#1.7
    print(Decimal('-1.45') + 0)#-1.5
    print(Decimal('-1.42') + 0)#-1.5
    ctx.rounding = ROUND_HALF_UP
    print(Decimal('1.65') + 0)#1.7
    print(Decimal('1.62') + 0)#1.6
    print(Decimal('-1.45') + 0)#-1.5
    ctx.rounding = ROUND_HALF_DOWN
    print(Decimal('1.65') + 0)#1.6
    print(Decimal('-1.45') + 0)#-1.4
    

    上机实践:2~14

    2. 编写程序,格式化输出杨辉三角

    杨辉三角即二项式定理的系数表,各元素满足如下条件:第一列及对角线上的元素均为1;其余每个元素等于它上一行同一列元素与前一列元素之和

    我使用了一个更加精妙的规律
    比如第一行为1
    第二行:01 + 10 = 11
    第三行:011 + 110 = 121
    第四行:0121 + 1210 = 1331
    。。。

    def generate(numRows):
        l1 = [1]
        n = 0
        while n < numRows:
            print(str(l1).center(66))
            l1 = [sum(t) for t in zip([0] + l1, l1 + [0])]  #利用zip函数算出每一行 如第二行 zip([0,1],[1,0])=[1,1],以此类推
            n += 1
    a=int(input("请输入行数"))
    generate(a)
    

    运行:

    请输入行数4
                                   [1]                                
                                  [1, 1]                              
                                [1, 2, 1]                             
                               [1, 3, 3, 1]  
    

    3. 输入直角三角形的两个直角边,求三角形的周长和面积,以及两个锐角的度数。结果保留一位小数

    import math
    
    a = float(input("请输入直角三角形的直角边a:"))
    b = float(input("请输入直角三角形的直角边b:"))
    c = math.sqrt(a*a+b*b)
    
    p = a + b + c
    area = 0.5*a*b
    print("三角形的周长:{0:1.1f},面积:{1:1.1f}".format(p,area))
    
    sina = a/c
    sinb = b/c
    
    a_degree = round(math.asin(sina) * 180 / math.pi,0)
    b_degree = round(math.asin(sinb) * 180 / math.pi,0)
    
    print("三角形直角边a的度数:{0},b的度数:{1}".format(a_degree,b_degree))
    

    运行:

    请输入直角三角形的直角边a:3
    请输入直角三角形的直角边b:4
    三角形的周长:12.0,面积:6.0
    三角形直角边a的度数:37.0,b的度数:53.0
    

    4. 编程产生0~100(包含0和100)的三个随机数a、b、c,要求至少使用两种不同的方法,将三个数按从小到大的顺序排序

    方法一:

    import random
    
    a = random.randint(0, 100)
    b = random.randint(0, 100)
    c = random.randint(0, 100)
    
    print(str.format("原始值:{0},{1},{2}", a, b, c))
    
    if(a > b): a,b = b,a
    if(a > c): a,c = c,a
    if(b > c): b,c = c,b
    
    print(str.format("增序:{0},{1},{2}", a, b, c))
    

    方法二(使用内置函数max、min、sum):

    import random
    
    a = random.randint(0, 100)
    b = random.randint(0, 100)
    c = random.randint(0, 100)
    
    print(str.format("原始值:{0},{1},{2}", a, b, c))
    
    maxx = max(a, b, c)
    minx = min(a, b, c)
    median = sum([a, b, c]) - minx - maxx
    
    print(str.format("增序:{0},{1},{2}", minx, median, maxx))
    

    方法三(使用内置函数sorted):

    >>> import random
    >>> a = random.randint(0,100)
    >>> b = random.randint(0,100)
    >>> c = random.randint(0,100)
    >>> print("init value: {0} , {1} , {2}".format(a,b,c))
    init value: 17 , 6 , 59
    >>> sorted([a,b,c])
    [6, 17, 59]
    

    5. 编程计算有固定工资收入的党员每月所缴纳的党费。

    工资基数3000元及以下者,交纳工资基数的0.5%
    工资基数3000~5000元者,交纳工资基数的1%
    工资基数在5000~10000元者,交纳工资基数的1.5%
    工资基数超过10000元者,交纳工资基数的2%

    salary = float(input("请输入有固定工资收入的党员的月工资:"))
    if salary <= 3000: dues = salary*0.005
    elif salary <= 5000: dues = salary*0.01
    elif salary <= 10000: dues = salary*0.15
    else: dues = salary*0.02
    
    print("交纳党费:",dues)
    

    运行:

    请输入有固定工资收入的党员的月工资:10001
    交纳党费: 200.02
    

    6. 编程实现袖珍计算器,要求输入两个操作数和一个操作符(+、-、*、/、%),根据操作符输出运算结果。注意/和%运算符的零异常问题

    a = float(input("请输入操作数(左):"))
    b = float(input("请输入操作数(右):"))
    operator = input("请输入操作符(+、-、*、/、%):")
    
    if(b == 0 and (operator == '/' or operator == '%')):
        print("分母为零,异常!")
    else:
        if operator == '+': result = a+b
        elif operator == '-': result = a-b
        elif operator == '*': result = a*b
        elif operator == '/': result = a/b
        elif operator == '%': result = a%b
        print("{0} {1} {2}= {3}:".format(a,operator,b,result))
    

    运行:

    请输入操作数(左):10
    请输入操作数(右):5
    请输入操作符(+、-、*、/、%):+
    10.0 + 5.0= 15.0:
    

    7. 输入三角形的3条边a、b、c,判断此3边是否可以构成三角形。若能,进一步判断三角形的性质,即为等边、等腰、直角或其他三角形

    a = float(input("请输入三角形的边a:"))
    b = float(input("请输入三角形的边b:"))
    c = float(input("请输入三角形的边c:"))
    
    if(a > b): a,b = b,a
    if(a > c): a,c = c,a
    if(b > c): b,c = c,b
    
    result = "三角形"
    if(not(a>0 and b>0 and c>0 and a+b>c)):
        result = '此三边无法构成三角形'
    else:
        if a == b == c: result = '等边三角形'
        elif(a==b or a==c or b==c): result = '等腰三角形'
        elif(a*a+b*b == c*c): result = '直角三角形'
    
    print(result)
    

    运行:

    请输入三角形的边a:3
    请输入三角形的边b:4
    请输入三角形的边c:5
    直角三角形
    

    8. 编程实现鸡兔同笼问题

    已知在同一个笼子里共有h只鸡和兔,鸡和兔的总脚数为f,其中h和f由用户输入,求鸡和兔各有多少只?要求使用两种方法:一是求解方程;二是利用循环进行枚举测试

    h = int(input("请输入总头数:"))
    f = int(input("请输入总脚数:"))
    
    def fun1(h,f):
        rabbits = f/2-h
        chicken = h-rabbits
        if(chicken < 0 or rabbits < 0): return '无解'
        return chicken,rabbits
    
    def fun2(h,f):
        for i in range(0,h+1):
            if(2*i + 4*(h-i) == f):return i,h-i
        return '无解'
    
    if(h>0 and f>0 and f % 2 == 0):
        if fun1(h,f)=='无解':
            print("无解")
        else:
            print("方法一:鸡:{0},兔:{1}".format(fun1(h,f)[0],fun1(h,f)[1]))
            print("方法二:鸡:{0},兔:{1}".format(fun2(h,f)[0],fun2(h,f)[1]))
    else:
        print('输入的数据无意义')    
    

    运行:

    请输入总头数:100
    请输入总脚数:100
    无解
    

    9. 输入任意实数x,计算ex的近似值,直到最后一项的绝对值小于10-6为止

    ex = 1 + x + x2/2 + x3/3! + x4/4! + … + xn/n!

    x = int(input("请输入任意实数:"))
    
    e = 1
    i = 1
    t = 1
    a = 1
    while(a >= 10e-6):
        t *= i
        a = pow(x,i)/t
        e += a
        i += 1
    
    print(e)
    

    运行:

    请输入任意实数:1
    2.7182815255731922
    

    我发现了在Python中10e-6pow(10,-6)是有差别的,将上述代码中的10e-6改为pow(10,-6),输出结果会有细微的差别

    运行:

    请输入任意实数:1
    2.7182818011463845
    

    10. 输入任意实数a(a>=0),用迭代法求x=√a,要求计算的相对偏差小于10-6

    求平方根的公式:

    Xn+1 = 0.5(Xn + a/Xn)

    import math
    
    a = int(input("请输入任意实数a(>=0):"))
    
    x = a / 2
    y = (x + a/x) / 2
    
    while(abs(y-x) >= pow(10,-6)):
        x = y
        y = (x + a/x) / 2
    
    print(y)
    

    运行:

    请输入任意实数a(>=0):2
    1.414213562373095
    

    11. 即有一个数,用3除余2,用5除余3,用7除余2,请问0~1000中这样的数有哪些?

    我国古代有位大将,名叫韩信。他每次集合部队,只要求部下先后按1-3,1-5,1-7报数,然后再报告一下各队每次报数的余数,他就知道到了多少人。他的这种巧妙算法被人们称作“鬼谷算”,也叫“隔墙算”,或称为“韩信点兵”,外国人还称它为“中国余数定理”。

    for i in range(0,1001):
        if((i % 3 == 2 )and (i % 5 == 3) and (i % 7 == 2)): print(i, end="  ")
    

    运行:

    23  128  233  338  443  548  653  758  863  968
    

    12. 一球从100米的高度自由下落,每次落地后反弹回原高度的一半,再落下。求小球在第10次落地时共经过多少米?第10次反弹多高

    规律:
    第一次下落时的高度:100
    第二次下落时的高度(第一次反弹的高度):50
    第三次下落时的高度(第二次反弹的高度):25

    n = 10
    
    h_down = 100
    h_up = 0
    sum = 0
    for i in range(1,n+1):
        sum += h_down+h_up
        h_down = h_up = h_down/2
    
    print("小球在第十次落地时共经过:{0}米,第十次反弹高度:{1}米".format(sum,h_up))    
    

    运行:

    小球在第十次落地时共经过:299.609375米,第十次反弹高度:0.09765625米
    

    13. 猴子吃桃问题

    猴子第一天摘下若干个桃子,当天吃掉一半多一个;第二天接着吃了剩下的桃子的一半多一个;以后每天都吃了前一天剩下的桃子的一半多一个。到第八天发现只剩一个桃子了。请问猴子第一天共摘了多少个桃子?

    这是一个递推问题

    某天所剩桃子数x
    后一天所剩桃子数y = x - (x/2+1) = x/2-1

    则x = 2(y+1)

    result = 1
    for i in range(8,0,-1):
        print("第{0}天桃子数:{1}".format(i,result))
        result = 2*(result+1)
    

    运行:

    第8天桃子数:1
    第7天桃子数:4
    第6天桃子数:10
    第5天桃子数:22
    第4天桃子数:46
    第3天桃子数:94
    第2天桃子数:190
    第1天桃子数:382
    

    14. 计算Sn = 1+11+111+…+111…111(最后一项是n个1)。n是一个随机产生的1~10(包括1和10)中的正整数

    import random
    
    n = random.randint(1,10)
    
    x = 1
    s = 0
    for i in range(1,n+1):
        s += x
        x = 10*x+1
    
    print("n = {0},sn = {1}".format(n,s))
    

    运行:

    n = 6,sn = 123456
    

    random.randint(a, b)

    • 生成指定范围内的整数
    • 范围:[a, b]

    案例研究:科学计算和数据分析

    https://blog.csdn.net/Zhangguohao666/article/details/103941448

    通过Python科学计算和数据分析库的安装和基本使用,了解使用Python进行科学计算的基本方法

    第五章 序列数据类型


    几个例题

    一:Python中内置的序列数据类型

    • 元组也称为定值表,用于存储固定不变的表
    • 列表也称为表,用于存储其值可变的表
    • 字符串是包括若干字符的序列数据,支持序列数据的基本操作
    • 字节序列数据是包括若干字节的序列。Python抓取网页时返回的页面通常为utf-8编码的字节序列。

    字节序列和字符串可以直接相互转换(字节编码和解码):

    >>> s1 = b'abc'
    >>> s1
    b'abc'
    >>> s1.decode("utf-8")
    abc
    
    >>> s2 = "中国"
    >>> s2.encode("utf-8")
    b'\xe4\xb8\xad\xe5\x9b\xbd'
    

    二:序列的切片操作示例

    >>> s = 'zgh666'
    >>> s[0]
    'z'
    >>> s[2]
    'h'
    >>> s[:3]
    'zgh'
    >>> s[1:3]
    'gh'
    >>> s[3:6]
    '666'
    >>> s[3:55]
    '666'
    >>> s[::-1]
    '666hgz'
    >>> s[3:2]
    ''
    >>> s[:]
    'zgh666'
    >>> s[::2]
    'zh6'
    

    三:序列的连接和重复操作

    • 通过连接操作符+可以连接两个序列,形成一个新的序列对象
    • 通过重复操作符*可以重复一个序列n次
    • 连接操作符和重复操作符也支持复合赋值运算,即:+=*=
    >>> x = 'zgh'
    >>> y = '666'
    >>> x + y
    'zgh666'
    >>> x *2
    'zghzgh'
    >>> x += y
    >>> x
    'zgh666'
    >>> y *= 3
    >>> y
    '666666666'
    

    四:序列的成员关系操作

    • in
    • not in
    • s.count(x)
      x在s中出现的次数
    • s.index(x)
      x在s中第一次出现的下标
    >>> s = "zgh666"
    >>> 'z' in s
    True
    >>> 'g' not in s
    False
    >>> s.count('6')
    3
    >>> s.index('6')
    3
    

    五:序列的排序操作

    sorted(iterable,key=None,reverse=False)

    >>> sorted(s)
    [1, 3, 5, 9]
    >>> sorted(s,reverse=True)
    [9, 5, 3, 1]
    
    >>> s = 'zGhZgH'
    >>> sorted(s)
    ['G', 'H', 'Z', 'g', 'h', 'z']
    >>> sorted(s,key=str.lower)
    ['G', 'g', 'h', 'H', 'z', 'Z']
    >>> sorted(s,key=str.lower,reverse=True)
    ['z', 'Z', 'h', 'H', 'G', 'g']
    

    六:序列的拆分

    1. 变量个数与序列长度相等
      若变量个数与序列的元素个数不一致,将导致ValueError
    >>> data = (118,'zgh',(100,100,100))
    >>> sid,name,(chinese,english,math) = data
    >>> sid
    118
    >>> name
    'zgh'
    >>> chinese
    100
    >>> english
    100
    >>> math
    100
    
    1. 变量个数与序列长度不等
      如果序列长度未知,可以使用*元组变量,将多个值作为元组赋值给元组变量。在一个赋值语句中,*元组变量只允许出现一次,否则将导致SyntaxError
    >>> first,second,third,*middles,last = range(10)
    >>> first
    0
    >>> second
    1
    >>> third
    2
    >>> middles
    [3, 4, 5, 6, 7, 8]
    >>> last
    9
    
    >>> first,*middles,last = sorted([58,60,60,100,70,70])
    >>> sum(middles)/len(middles)
    65.0
    
    1. 使用临时变量_
      如果只需要部分数据,序列的其它位置可以使用临时变量_
    >>> record = ['zgh','858990471@qq.com','17354364147','15272502101']
    >>> name,_,*phone = record
    >>> name
    'zgh'
    >>> phone
    ['17354364147', '15272502101']
    

    七:使用元组字面量,tuple创建元组实例对象的实例

    >>> t1 = 1,2,3
    >>> t1
    (1, 2, 3)
    
    >>> t2 = (4,5,6)
    >>> t2
    (4, 5, 6)
    
    >>> t3 = (9,)
    >>> t3
    (9,)
    

    如果元组中只有一个项目,后面的逗号不能省略。

    Python解释器把(1)解释为整数1,将(1,)解释为元组

    >>> t1 = tuple()
    >>> t1
    ()
    
    >>> t2 = tuple("zgh666")
    >>> t2
    ('z', 'g', 'h', '6', '6', '6')
    
    >>> t3 = tuple(['z','g','h'])
    >>> t3
    ('z', 'g', 'h')
    

    八:使用列表字面量,list创建列表实例对象的实例

    >>> l1 = []
    >>> l1
    []
    
    >>> l2 = ['zgh666']
    >>> l2
    ['zgh666']
    
    >>> l3 = [(1,2,3)]
    >>> l3
    [(1, 2, 3)]
    
    >>> l1 = list()
    >>> l1
    []
    
    >>> l2 = list(b'zgh666')
    >>> l2
    [122, 103, 104, 54, 54, 54]
    
    >>> l3 = list(b'aAbBcC')
    >>> l3
    [97, 65, 98, 66, 99, 67]
    

    补充:列表是可变对象,故用户可以改变列表对象中元素的值,也可以通过del删除某元素

    九:列表解析表达式示例

    使用列表解析表达式可以简单,高效地处理一个可迭代对象,并生成结果列表

    >>> [(i,i**2) for i in range(10)]
    [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49), (8, 64), (9, 81)]
    
    >>> [i for i in range(10) if i%2==0]
    [0, 2, 4, 6, 8]
    
    >>> [(x,y,x*y) for x in range(1,4) for y in range(1,4) if x>=y]
    [(1, 1, 1), (2, 1, 2), (2, 2, 4), (3, 1, 3), (3, 2, 6), (3, 3, 9)]
    

    选择题:4、5、7、11、12

    4. Python语句序列“a = (1,2,3,None,(),[]);print(len(a))”的运行结果是

    >>> a = (1,2,3,None,(),[])
    >>> len(a)
    6
    

    5. Python语句序列“nums = set([1,2,2,3,3,3,4]);print(len(nums))”的运行结果是

    >>> nums = set([1,2,2,3,3,3,4])
    >>> nums
    {1, 2, 3, 4}
    >>> len(nums)
    4
    

    7. Python语句序列“s1=[4,5,6];s2=s1;s1[1]=0;print(s2)”的运行结果是

    Python中变量(如s1,s2)存储在栈中,存放的是地址
    [4,5,6]存储在堆中

    s1 = [4,5,6]即s1存储指向堆中[4,5,6]的地址
    s2 = s1地址赋值,即s2和s1都指向同一个地址
    所以对列表进行修改,两者的显示都会发生变化

    >>> s1 = [4,5,6]
    >>> s2 = s1
    >>> s1[1] = 0
    >>> s1
    [4, 0, 6]
    >>> s2
    [4, 0, 6]
    

    11. Python语句序列“s={‘a’,1,‘b’,2};print(s[‘b’])”的运行结果是

    A. 语法错B. ‘b’C. 1D. 2

    答案:A

    通过值访问集合是没有意义的,语法也不支持

    >>> s ={'a',1,'b',2}
    >>> print(s['b'])
    Traceback (most recent call last):
      File "<pyshell#29>", line 1, in <module>
        print(s['b'])
    TypeError: 'set' object is not subscriptable
    

    补充:集合set是无序不重复的,是无法通过下标访问的

    12. Python语句print(r"\nGood")的运行结果是

    A. 新行和字符串GoodB. r"\nGood"C. \nGoodD. 字符r、新行和字符串Good

    答案:C

    >>> print(r"\nGood")
    \nGood
    

    r""声明原始字符串

    填空题:1、5、6、12、13、14

    1. Python语句序列“fruits = [‘apple’,‘banana’,‘bear’];print(fruits[-1][-1])”的运行结果是

    注意:fruit[-1]是字符串’bear’
    所以:fruit[-1][-1]'bear[-1]'

    >>> fruits = ['apple','banana','pear']
    >>> fruits[-1]
    'pear'
    >>> fruits[-1][-1]
    'r'
    

    5. Python语句 print(’%d%%%d’%(3/2,3%2)) 的运行结果是

    >>> print('%d%%%d'%(3/2,3%2))
    1%1
    

    6. Python语句序列“s = [1,2,3,4];s.append([5,6]);print(len(s))”的运行结果是

    答案:5

    注意append()和extend()函数的区别
    s.append(x)将对象x追加到s尾部
    s.extend(x)将序列x追加到s尾部

    append

    >>> s = [1,2,3,4]
    >>> s.append([5,6])
    >>> s
    [1, 2, 3, 4, [5, 6]]
    >>> len(s)
    5
    

    extend

    >>> s = [1,2,3,4]
    >>> s.extend([5,6])
    >>> s
    [1, 2, 3, 4, 5, 6]
    >>> len(s)
    6
    

    12

    >>> s =('a','b','c','d','e')
    >>> s[2]
    'c'
    >>> s[2:3]
    ('c',)
    >>> s[2:4]
    ('c', 'd')
    >>> s[1::2]
    ('b', 'd')
    >>> s[-2]
    'd'
    >>> s[::-1]
    ('e', 'd', 'c', 'b', 'a')
    >>> s[-2:-1]
    ('d',)
    >>> s[-99:-5]
    ()
    >>> s[-99:-3]
    ('a', 'b')
    >>> s[::]
    ('a', 'b', 'c', 'd', 'e')
    >>> s[1:-1]
    ('b', 'c', 'd')
    

    13

    >>> s = [1,2,3,4,5,6]
    >>> s[:1] = []
    >>> s
    [2, 3, 4, 5, 6]
    
    >>> s[:2] = 'a'
    >>> s
    ['a', 4, 5, 6]
    
    >>> s[2:] = 'b'
    >>> s
    ['a', 4, 'b']
    
    >>> s[2:3] = ['x','y']
    >>> s
    ['a', 4, 'x', 'y']
    
    >>> del s[:1]
    >>> s
    [4, 'x', 'y']
    

    14

    >>> s = ['a','b']
    >>> s.append([1,2])
    >>> s
    ['a', 'b', [1, 2]]
    >>> s.extend('34')
    >>> s
    ['a', 'b', [1, 2], '3', '4']
    >>> s.extend([5,6])
    >>> s
    ['a', 'b', [1, 2], '3', '4', 5, 6]
    >>> s.insert(1,7)
    >>> s
    ['a', 7, 'b', [1, 2], '3', '4', 5, 6]
    >>> s.insert(10,8)
    >>> s
    ['a', 7, 'b', [1, 2], '3', '4', 5, 6, 8]
    >>> s
    ['a', 7, 'b', [1, 2], '3', '4', 5, 6]
    >>> s.remove('b')
    >>> s
    ['a', 7, [1, 2], '3', '4', 5, 6]
    >>> s[3:] =[]
    >>> s
    ['a', 7, [1, 2]]
    >>> s.reverse()
    >>> s
    [[1, 2], 7, 'a']
    >>> 
    

    思考题:2、3、5

    2. 阅读下面的Python语句,请问输出结果是什么?

    n = int(input('请输入图形的行数:'))
    
    for i in range(n,0,-1):
        print(" ".rjust(20-i),end=' ')
        for j in range(2*i-1):print(" * ",end=' ')
        print('\n')
    
    for i in range(1,n):
        print(" ".rjust(19-i),end=' ')
        for j in range(2*i+1):print(" * ",end=' ')
        print('\n')          
    

    运行一:

    请输入图形的行数:1
                         *  
    

    运行二:

    请输入图形的行数:2
                        *   *   *  
    
                         *  
    
                        *   *   *  
    

    运行三:

    请输入图形的行数:3
                       *   *   *   *   *  
    
                        *   *   *  
    
                         *  
    
                        *   *   *  
    
                       *   *   *   *   *  
    

    3. 阅读下面的Python语句,请问输出结果是什么?

    n = int(input('请输入上(或下)三角行数:'))
    
    for i in range(0,n):
        print(" ".rjust(19-i),end=' ')
        for j in range(2*i+1):print(" * ",end=' ')
        print('\n')
    
    for i in range(n-1,0,-1):
        print(" ".rjust(20-i),end=' ')
        for j in range(2*i-1):print(" * ",end=' ')
        print('\n')          
    

    运行:

    请输入上(或下)三角行数:4
                         *  
    
                        *   *   *  
    
                       *   *   *   *   *  
    
                      *   *   *   *   *   *   *  
    
                       *   *   *   *   *  
    
                        *   *   *  
    
                         *  
    

    5. 阅读下面的Python语句,请问输出结果是什么?

    先看这三句:

    >>> names1 = ['Amy','Bob','Charlie','Daling']
    >>> names2 = names1
    >>> names3 = names1[:]
    

    毫无疑问,此时names1,names2,names3的值都是[‘Amy’,‘Bob’,‘Charlie’,‘Daling’]
    但是

    >>> id(names1)
    2338529391368
    >>> id(names2)
    2338529391368
    >>> id(names3)
    2338529391560
    

    names1和names2指向同一个地址
    而names3指向另一个地址

    然后:

    >>> names2[0] = 'Alice'
    >>> names3[1] = 'Ben'
    >>> names1
    ['Alice', 'Bob', 'Charlie', 'Daling']
    >>> names2
    ['Alice', 'Bob', 'Charlie', 'Daling']
    >>> names3
    ['Amy', 'Ben', 'Charlie', 'Daling']
    

    最后:

    >>> sum = 0
    >>> for ls in(names1,names2,names3):
    	if ls[0] == 'Alice': sum+=1
    	if ls[1] == 'Ben':sum+=2
    
    	
    >>> print(sum)
    4
    

    上机实践:2~6

    2. 统计所输入字符串中单词的个数,单词之间用空格分隔

    s = input("请输入字符串:")
    
    num = 0
    for i in s:
        if((i >= 'a' and i <= 'z') or (i >= 'A' and i <= 'Z')):
            num += 1
    
    print("其中的单词总数:",num) 
    

    运行:

    请输入字符串:zgh666 ZGH6
    其中的单词总数: 6
    

    3. 编写程序,删除一个list里面重复元素

    方法一:利用set集合不重复的性质(但结果不能保证原来的顺序)

    l = [1,2,2,3,3,3,4,5,6,6,6]
    s = set(l)
    l = list(s)
    print(l)
    

    运行:

    [1, 2, 3, 4, 5, 6]
    

    方法二:既可以去除重复项,又可以保证原来的顺序

    def unique(items):
        items_existed = set()
        for item in items:
            if item not in items_existed:
                yield item
                items_existed.add(item)
    
    if __name__ == '__main__':
        a = [1, 8, 5, 1, 9, 2, 1, 10]
        a1 = unique(a)
        print(list(a1))
    
    

    运行结果:

    [1, 8, 5, 9, 2, 10]
    

    对代码的分析:

    • 可以看出,unique()函数返回的并不是items_existed,而是利用了yield

    在函数定义中,如果使用yield语句代替return返回一个值,则定义了一个生成器函数(generator)
    生成器函数是一个迭代器,是可迭代对象,支持迭代

    • a1 = unique(a) 这个函数返回的实际上是一个可迭代对象
      print(a1)得到的会是:<generator object unique at 0x0000016E23AF4F48>
    • 所以,要得到去掉重复后的列表的样子,需要将可迭代对象a1放在list()中
      运行:

    4. 编写程序,求列表[9,7,8,3,2,1,55,6]中的元素个数、最大值、最小值,以及元素之和、平均值。请思考有几种实现方法?

    内置函数:

    s = [9,7,8,3,2,1,55,6]
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(len(s),max(s),min(s),sum(s),sum(s)/len(s)))
    

    直接访问元素列表(for i in s…):

    s = [9,7,8,3,2,1,55,6]
    
    sum = 0
    max = s[0]
    min = s[0]
    length = 0
    for i in s:
        sum += i
        length += 1
        if(i > max): max = i
        if(i < min): min = i
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(length,max,min,sum,sum/length))
    
    

    间接访问列表元素(for i in range(0,len(s))…):

    s = [9,7,8,3,2,1,55,6]
    
    sum = 0
    max = s[0]
    min = s[0]
    length = len(s)
    for i in range(0,length):
        sum += s[i]
        if(s[i] > max): max = s[i]
        if(s[i] < min): min = s[i]
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(length,max,min,sum,sum/length))
    
    

    正序访问(i=0;while i<len(s)…):

    s = [9,7,8,3,2,1,55,6]
    
    sum = 0
    max = s[0]
    min = s[0]
    length = len(s)
    
    i = 0
    while(i < length):
        sum += s[i]
        if(s[i] > max): max = s[i]
        if(s[i] < min): min = s[i]
        i += 1
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(length,max,min,sum,sum/length))
    
    

    反序访问(i=len(s)-1;while i>=0…):

    s = [9,7,8,3,2,1,55,6]
    
    sum = 0
    max = s[0]
    min = s[0]
    length = len(s)
    
    i = length-1
    while(i >= 0):
        sum += s[i]
        if(s[i] > max): max = s[i]
        if(s[i] < min): min = s[i]
        i -= 1
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(length,max,min,sum,sum/length))
    
    

    while True:…break

    s = [9,7,8,3,2,1,55,6]
    
    sum = 0
    max = s[0]
    min = s[0]
    length = len(s)
    
    i = 0
    while(True):
        if(i > length-1): break
        sum += s[i]
        if(s[i] > max): max = s[i]
        if(s[i] < min): min = s[i]
        i += 1
    
    print("元素个数:{0},最大值:{1},最小值:{2},和:{3},平均值:{4}".\
          format(length,max,min,sum,sum/length))
    

    运行:

    元素个数:8,最大值:55,最小值:1,和:91,平均值:11.375
    

    5. 编写程序,将列表[9,7,8,3,2,1,5,6]中的偶数变成它的平方,奇数保持不变

    l = [9,7,8,3,2,1,5,6]
    
    for i,value in enumerate(l):
        if(value % 2 == 0):l[i] = value**2
    
    print(l)
    

    运行:

    [9, 7, 64, 3, 4, 1, 5, 36]
    

    6. 编写程序,输入字符串,将其每个字符的ASCII码形成列表并输出

    s = input("请输入一个字符串:")
    l = list()
    for i in s:
        l.append(ord(i))
    
    print(l)
    

    运行:

    请输入一个字符串:zgh666
    [122, 103, 104, 54, 54, 54]
    

    案例研究:猜单词游戏

    https://blog.csdn.net/Zhangguohao666/article/details/103948234

    通过猜单词游戏的设计和实现,帮助读者了解使用Python系列数据类型和控制流程

    第六章 输入和输出


    几个例题

    一:运行时提示输入密码

    输入密码时,一般需要不明显,则可以使用模块getpass,以保证用户输入的密码在控制台中不回显

    import getpass
    
    username = input("user:")
    password = getpass.getpass("password:")
    if(username == 'zgh' and password == '666'):
        print('logined!')
    else:
        print('failed!')
    
    input()#为了看到输出结果。因为执行完毕后,控制台会立马关闭
    

    注意:上面这个代码,如果使用IDLE执行,会因为安全问题而执行失败

    但是,在控制台中执行就没问题,看输出结果(可以看到,输入的密码不会显示出来):

    user:zgh
    password:
    logined!
    

    二:重定向标准输出到一个文件的示例

    这种重定向由控制台完成,而与Python本身无关。

    格式:
    程序 > 输出文件

    其目的是将显示屏从标准输出中分离,并将输出文件与标准输出关联,即程序的执行结果将写入输出文件,而不是发送到显示屏中显示

    首先准备一个test.py文件(代码如下)

    import sys,random
    
    n = int(sys.argv[1])
    for i in range(n):
        print(random.randrange(0,100))
    

    然后在PowerShell中:python test.py 100 > scores.txt
    记住,切记,一定要注意:千万能省略python,这样写./test.py 100 > scores.txt会出现问题,生成的scores文件中会没有任何内容!!!(原因未知)

    然后在当前目录下,100个[0,100)范围内的的整数生成在scores.txt文件中了

    三:重定向文件到标准输入

    格式:
    程序 < 输入文件

    其目的是将控制台键盘从标准输入中分离,并将输入文件与标准输入关联,即程序从输入文件中读取输入数据,而不是从键盘中读取输入数据

    准备一个average.py文件(代码如下)

    import sys
    
    total =0.0
    count = 0
    for line in sys.stdin:
        count += 1
        total += float(line)
    
    avg = total/count
    print("average:",avg)
    

    然后问题总是不期而至,
    在PowerShell中:python average.py < scores.txt,会报错,PowerShell会提示你:“<”运算符是为将来使用而保留的
    很无奈,我只能使用cmd了,然后得出结果

    四:管道

    格式:
    程序1 | 程序2 | 程序3 | … | 程序4

    其目的是将程序1的标准输出连接到程序2的标准输入,
    将程序2的标准输出连接到程序3的标准输入,以此类推

    例如:
    打开cmd,输入python test.py 100 | average.py,其执行结果等同于上面两个例子中的命令

    使用管道更加简洁,且不用创建中间文件,从而消除了输入流和输出流可以处理的数据大小的限制,执行效率更高

    五:过滤器

    1. 使用操作系统实用程序more逐屏显示数据

    2. 使用操作系统实用程序sort排序输出数据

    more和sort都可以在一个语句中使用

    填空题:1、2

    print(value, ..., sep = ' ', end = '\n', file = sys.stdout, flush = False)

    1. sep(分隔符,默认为空格)
    2. end(换行符,即输入的末尾是个啥)
    3. file(写入到指定文件流,默认为控制台sys.stdout)
    4. flush(指定是否强制写入到流)

    1

    >>> print(1,2,3,4,5,sep='-',end='!')
    1-2-3-4-5!
    

    2

    >>> for i in range(10):
    	print(i,end=' ')
    
    	
    0 1 2 3 4 5 6 7 8 9 
    

    例题及上机实践:2~5

    2. 尝试修改例6.2编写的命令行参数解析的程序,解析命令行参数所输入边长的值,计算并输出正方形的周长和面积

    argparse模块用于解析命名的命令行参数,生成帮助信息的Python标准模块

    例6.2:解析命令行参数所输入的长和宽的值,计算并输出长方形的面积

    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--length', default = 10, type = int, help = '长度')
    parser.add_argument('--width', default = 5, type = int, help = '宽度')
    
    args = parser.parse_args()
    area = args.length * args.width
    print('面积 = ', area)
    
    input()#加这一句是为了可以看到输出结果
    

    输出:面积 = 50

    如果在执行这个模块时,加入两个命令行参数

    输出:面积 = 36

    基本上看了上面这个例子后,就可以理解argparse的用法了

    本题代码:

    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--length', default = 10, type = int, help = '长度')
    
    args = parser.parse_args()
    area = args.length ** 2
    perimeter = 4 * args.length
    print('面积 = {0},周长 = {1}'.format(area,perimeter))
    
    input()#加这一句是为了可以看到输出结果
    
    

    在PowerShell中输入.\test.py
    不给命令行参数,输出是以默认值来计算的
    输出:面积 = 100,周长 = 40

    给命令行参数:.\test.py --length 1
    输出:面积 = 1,周长 = 4

    3. 尝试修改例6.8编写读取并输出文本文件的程序,由命令行第一个参数确认所需输出的文本文件名

    f = open(file, mode = 'r' , buffering = -1, encoding = None)

    1. file是要打开或创建的文件名,如果文件不在当前路径,需指出具体路径
    2. mode是打开文件的模式,模式有:
      ‘r’(只读)
      ‘w’(写入,写入前删除就内容)
      ‘x’(创建新文件,如果文件存在,则导致FileExistsError)
      ‘a’(追加)
      ‘b’(二进制文件)
      ‘t’(文本文件,默认值)
      ‘+’(更新,读写)
    3. buffering表示是否使用缓存(缓存为-1,表示使用系统默认的缓冲区大小)
    4. encoding是文件的编码

    例6.8:读取并输出文本文件

    import sys
    
    filename = sys.argv[0]#就读取本文件,骚的呀皮
    f = open(filename, 'r', encoding = 'utf-8')
    
    line_no = 0
    while True:
        line_no += 1
        line = f.readline()
        if line:
            print(line_no, ":", line)
        else:
            break
    f.close()       
    

    输出(代码输出的就是本python文件):

    1 : import sys
    
    2 : 
    
    3 : filename = sys.argv[0]#就读取本文件,骚的呀皮
    
    4 : f = open(filename, 'r', encoding = 'utf-8')
    
    5 : 
    
    6 : line_no = 0
    
    7 : while True:
    
    8 :     line_no += 1
    
    9 :     line = f.readline()
    
    10 :     if line:
    
    11 :         print(line_no, ":", line)
    
    12 :     else:
    
    13 :         break
    
    14 : f.close()
    
    15 :         
    
    

    本题代码:

    对例题代码进行些许修改就可以了,首先将上例中的第二个语句改为:filename = sys.argv[0],再考虑下面怎么进行

    准备一个用来测试的文件test.txt:

    对于这个文件要注意一点(你们很可能回出现这个问题!!!),win10默认创建的文本文件的字符编码是ANSI

    代码怎么写,有两种:

    1. 将test.txt文本文件的编码修改为utf-8,代码如上所说
      记事本方式打开test.txt文件,点击文件,点击另存为,看到下方的编码(修改为utf-8)
    2. test.txt就用默认的ANSI编码方式,再将上例代码的第三个语句修改为f = open(filename, 'r', encoding = 'ANSI')

    在PowerShell中输入:./test.py test.txt
    输出:

    1 : 大家好
    
    2 : 我是Zhangguohao666
    
    3 : 如果本文章对大家有帮助,请点赞支持一下
    
    4 : 还有:
    
    5 : 如果发现了什么问题,请在评论区指出,我会积极改进
    

    4. 尝试修改例6.9编写利用with语句读取并输出文本文件的程序,由命令行第一个参数确认所需输出的文本文件名

    为了简化操作,Python语言中与资源相关的对象可以实现上下文管理协议,可以使用with语句,确保释放资源。
    with open(file,mode) as f:

    例6.9:利用with语句读取并输出文本文件

    import sys
    
    filename = sys.argv[0]
    
    line_no = 0
    with open(filename, 'r', encoding = 'utf-8') as f:
        for line in f:
            line_no += 1
            print(line_no, ":", line)
    f.close()
    

    基本上,看这个例子,就可以上手with语句了

    本题代码:

    还是上一题准备的文本文件,
    代码一(文本文件的编码为默认的ANSI):

    import sys
    
    filename = sys.argv[1]
    
    line_no = 0
    with open(filename, 'r', encoding = 'ANSI') as f:
        for line in f:
            line_no += 1
            print(line_no, ":", line)
    f.close()
          
    

    代码二(将文本文件的编码修改为utf-8):

    import sys
    
    filename = sys.argv[1]
    
    line_no = 0
    with open(filename, 'r', encoding = 'utf-8') as f:
        for line in f:
            line_no += 1
            print(line_no, ":", line)
    f.close()
          
    
    

    本题的输出,我再不要脸的放一次吧:

    1 : 大家好
    
    2 : 我是Zhangguohao666
    
    3 : 如果本文章对大家有帮助,请点赞支持一下
    
    4 : 还有:
    
    5 : 如果发现了什么问题,请在评论区指出,我会积极改进
    

    5. 尝试修改例6.12编写标准输出流重定向的程序,从命令行第一个参数中获取n的值,然后将0-n,0-n的2倍值,2的0-n次幂的列表打印输出到out.log文件中

    例6.12:从命令行第一个参数中获取n的值,然后将0-n,2的0-n次幂的列表打印输出到out.log文件中

    1. 标准输入流文件对象:sys.stdin,
      默认值为sys.__stdin__
    2. 标准输出流文件对象:sys.stdout,
      默认值为sys.__stdout__
    3. 错误输出流文件对象(标准错误流文件对象):sys.stderr
      默认值为sys.__stderr__

    书中给的代码是这样的:

    import sys
    
    n = int(sys.argv[1])
    power = 1
    i = 0
    
    f = open('out.log', 'w')
    sys.stdout = f
    
    while i <= n:
        print(str(i), ' ', str(power))
        power = 2*power
        i += 1
    sys.stdout = sys.__stdout__
    

    如果使用的编辑器是PyCharm(现在大多数编辑器会帮你对代码进行优化和处理一些隐患),运行书中的这个代码没有问题。

    但是:
    若使用的编辑器是python自带的IDLE,运行这个代码有问题!

    第一:out.log文件会生成,但是没有东西
    (发现文件关闭不了(就是×不掉),
    确定是文件没关闭(f.close())的原因)

    第二:控制台没有输出’done’语句(估计是IDLE编辑器处理不了__stdout__这个值)

    经过研究后,发现(基于IDLE编辑器):
    如果在上面的代码中加入f.close()后,该输入的东西都成功输入进out.log文件了,
    但是,
    还有一个问题
    控制台依旧没有输出’done’语句
    经过一步步的断点调试(就是手动写print)
    发现sys.stdout = sys.__stdout__不会执行

    然后进行改动后,就可以了,代码如下:
    (既然__stdout__不好使,就使用中间变量)

    import sys
    
    n = int(sys.argv[1])
    power = 1
    i = 0
    
    output = sys.stdout
    f = open('out.log', 'w')
    sys.stdout = f
    
    while i <= n:
        print(str(i), ' ', str(power))
        power = 2*power
        i += 1
    
    f.close()
    sys.stdout = output
    print('done!')#这一句是用来检测上面的代码是否成功执行
    
    

    问题虽然解决,但是原因没有彻底弄清楚,求助。。。。。。

    本题代码:

    import sys
    
    n = int(sys.argv[1])
    power = 1
    i = 0
    
    output = sys.stdout
    f = open('out.log', 'w')
    sys.stdout = f
    
    while i <= n:
        print(str(i), ' ',  str(2*i),  ' ', str(power))
        power = 2*power
        i += 1
    
    f.close()
    sys.stdout = output
    print('done!')#这一句是用来检测上面的代码是否成功执行
    
    

    比如时输入的命令行参数是6
    输出:

    案例研究:21点扑克牌游戏

    https://blog.csdn.net/Zhangguohao666/article/details/103948545

    通过21点扑克牌游戏的设计和实现,了解使用Python数据类型、控制流程和输入输出

    第七章 错误和异常处理


    Python语言采用结构化的异常处理机制捕获和处理异常

    而我感觉,Python在这方面的知识点其实和Java的差不多

    几个例题

    一:程序的错误和异常处理

    1. 语法错误

    指源代码中的拼写错误,这些错误导致Python编译器无法把Python源代码转换为字节码,故也称之为编译错误

    1. 运行时错误

    在解释执行过程中产生的错误

    例如:

    • 程序中没有导入相关的模块,NameError
    • 程序中包括零除运算,ZeroDivisionError
    • 程序中试图打开不存在的文件,FileNotFoundError
    1. 逻辑错误

    程序可以执行(程序运行本身不报错),但执行结果不正确。
    对于逻辑错误,Python解释器无能为力,需要用户根据结果来调试判断

    大部分由程序错误而产生的错误和异常一般由Python虚拟机自动抛出。另外,在程序中如果判断某种错误情况,可以创建相应的异常类的对象,并通过raise语句抛出

    >>> a = -1
    >>> if(a < 0): raise ValueError("数值不能为负数")
    
    Traceback (most recent call last):
      File "<pyshell#9>", line 1, in <module>
        if(a < 0): raise ValueError("数值不能为负数")
    ValueError: 数值不能为负数
    >>> 
    

    在程序中的某个方法抛出异常后,Python虚拟机通过调用堆栈查找相应的异常捕获程序。如果找到匹配的异常捕获程序(即调用堆栈中的某函数使用try…except语句捕获处理),则执行相应的处理程序(try…except语句中匹配的except语句块)

    如果堆栈中没有匹配的异常捕获程序,则Python虚拟机捕获处理异常,在控制台打印出异常的错误信息和调用堆栈,并中止程序的执行

    二:try …except…else…finally

    try:
    	可能产生异常的语句
    except Exception1:
    	发生Exception1时执行的语句
    except (Exception2,Exception3):
    	发生Exception2或Exception3时执行的语句
    except Exception4 as e:
    	发生Exception4时执行的语句,Exception4的实例是e
    except:
    	捕获其他所有异常
    else:
    	无异常时执行的语句
    finally:
    	不管异常发生与否都保证执行的语句			
    

    except语句可以写多个,但是要注意一点:系统是自上而下匹配发生的异常,所以用户需要将带有最具体的(即派生类程度最高的)异常类的except写在前面

    三:创建自定义异常,处理应用程序中出现的负数参数的异常

    自定义异常类一般继承于Exception或其子类。自定义异常类的名称一般以Error或Exception为后缀

    >>> class NumberError(Exception):
        def __init__(self,data):
            Exception.__init__
            (self,data)
            self.data = data
        def __str__(self):
            return self.data + ':非法数值(<0)'
    
    >>> 
    >>> def total(data):
        total = 0
        for i in data:
            if i < 0: raise NumberError(str(i))
            total += 1
        return total
    
    >>> 
    >>> data1 = (44, 78, 90, 80, 55)
    >>> print("sum: ",total(data1))
    sum:  5
    >>> 
    >>> data2 = (44, 78, 90, 80, -1)
    >>> print("sum: ",total(data2))
    Traceback (most recent call last):
      File "<pyshell#24>", line 1, in <module>
        print("sum: ",total(data2))
      File "<pyshell#18>", line 4, in total
        if i < 0: raise NumberError(str(i))
    NumberError: -1:非法数值(<0>>> 
    

    四:断言处理

    用户在编写程序时,在调试阶段往往需要判断代码执行过程中变量的值等信息:

    1. 用户可以使用print()函数打印输出结果
    2. 也可以通过断点跟踪调试查看变量
    3. 但使用断言更加灵活

    assert语句和AssertionError

    断言的声明:

    • assert <布尔表达式>
      即:if __debug__: if not testexpression: raise AssertionError
    • assert <布尔表达式>,<字符串表达式>
      即:if __debug__: if not testexpression: raise AssertionError(data)
      字符串表达式(即data)是断言失败时输出的失败消息

    __debug__也是布尔值,Python解释器有两种:调试模式和优化模式

    • 调试模式:__debug__ == True
    • 优化模式:__debug__ == False

    在学习中,对于执行一个py模块(比如test.py)我们通常在cmd中这么输入python test.py,而这默认是调试模式。
    如果我们要使用优化模式来禁用断言来提高程序效率,我们可以加一个运行选项-O,在控制台中这么输入python -O test.py

    看一下断言的示例吧,理解一下用法:

    a =int(input("a: "))
    b =int(input("b: "))
    assert b != 0, '除数不能为零'
    c = a/b
    print("a/b = ", c)
    

    cmd出场:
    输入正确数值时:

    输入错误数值时:

    禁用断言,并且输入错误数值时:

    案例研究:使用调试器调试Python程序

    https://blog.csdn.net/Zhangguohao666/article/details/103948568

    了解使用Python调试器调试程序的方法

    第八章 函数和函数式编程


    一些知识点总结和几个例题

    Python中函数的分类:

    1. 内置函数
      在程序中可以直接使用
    2. 标准库函数
      Python语言安装程序同时会安装若干标准库,例如math、random等
    3. 第三方库函数
      Python社区提供了许多其它高质量的库,在下载、安装这些库后,通过import语句可以导入库
    4. 用户自定义函数
    • 函数名为有效的标识符(命名规则为全小写字母,可以使用下划线增加可阅读性,例如my_func()
    • 函数可以使用return返回值
      如果函数体中包含return语句,则返回值
      否则不返回,即返回值为空(None),无返回值的函数相当于其它编程语言中的过程

    调用函数之前程序必须先执行def语句,创建函数对象

    • 内置函数对象会自动创建
    • import导入模块时会执行模块中的def语句,创建模块中定义的函数
    • Python程序结构顺序通常为import语句>函数定义>全局代码

    一:产生副作用的函数,纯函数

    打印等腰三角形

    n = int(input("行数:"))
    
    def print_star(n):
        print((" * " * n).center(50))
    
    for i in range(1, 2*n, 2):
        print_star(i)
    

    输出:

    行数:5
                            *                         
                         *  *  *                      
                      *  *  *  *  *                   
                   *  *  *  *  *  *  *                
                *  *  *  *  *  *  *  *  *             
    

    上面代码中的print_star()是一个产生副作用的函数,其副作用是向标准输出写入若干星号

    • 副作用:例如读取键盘输入,产生输出,改变系统的状态等
    • 在一般情况下,产生副作用的函数相当于其它程序设计语言中的过程,可以省略return语句

    定义计算并返回第n阶调和数(1+1/2+1/3+…+1/n)的函数,输出前n个调和数

    def harmonic(n):
        total = 0.0
        for i in range(1, n+1):
            total += 1.0/i
        return total
    
    n = int(input("n:"))
    
    print("输出前n个调和数的值:")
    for i in range(1, n+1):
        print(harmonic(i))
    

    输出:

     n:8
    输出前n个调和数的值:
    1.0
    1.5
    1.8333333333333333
    2.083333333333333
    2.283333333333333
    2.4499999999999997
    2.5928571428571425
    2.7178571428571425         
    

    上面代码中的harmonic()是纯函数

    纯函数:给定同样的实际参数,其返回值唯一,且不会产生其它的可观察到的副作用

    注意:编写同时产生副作用和返回值的函数通常被认为是不良编程风格,但有一个例外,即读取函数。例如,input()函数既可以返回一个值,又可以产生副作用(从标准输入中读取并消耗一个字符串)

    二:传递不可变对象、可变对象的引用

    • 实际参数值默认按位置顺序依次传递给形式参数。如果参数个数不对,将会产生错误

    在调用函数时:

    1. 若传递的是不可变对象(例如:int、float、bool、str对象)的引用,则如果函数体中修改对象的值,其结果实际上是创建了一个新的对象
    i = 1
    
    def func(i,n):
        i += n
        return i
    
    print(i)#1
    func(i,10)
    print(i)#1
    

    执行函数func()后,i依旧为1,而不是11

    1. 若传递的是可变对象(例如:list对象)的引用,则在函数体中可以直接修改对象的值
    import random
    
    def shuffle(a):
        n = len(a)
        for i in range(n):
            r = random.randrange(i,n)
            a[i],a[r] = a[r],a[i]
    
    a = [1,2,3,4,5]
    print("初始:",a)
    shuffle(a)
    print("调用函数后:",a)
    

    输出:

    初始: [1, 2, 3, 4, 5]
    调用函数后: [1, 5, 4, 3, 2]
    

    三:可选参数,命名参数,可变参数,强制命名参数

    可选参数

    • 在声明函数时,如果希望函数的一些参数是可选的,可以在声明函数时为这些参数指定默认值
    >>> def babbles(words, times=1):
    	print(words * times)
    
    	
    >>> babbles('Hello')
    Hello
    >>> 
    >>> babbles("Hello", 2)
    HelloHello
    >>> 
    

    注意到一点:必须先声明没有默认值的形参,然后再声明有默认值的形参,否则报错。 这是因为在函数调用时默认是按位置传递实际参数的。

    怎么理解上面那句话呢?

    默认是按位置传递实际参数(如果有默认值的形参在左边,无默认值的形参在右,那么在调用函数时,你的实参该怎么传递呢?)

    命名参数

    • 位置参数:当函数调用时,实参默认按位置顺序传递形参
    • 命名参数(关键字参数):按名称指定传入的参数
      参数按名称意义明确
      传递的参数与顺序无关
      如果有多个可选参数,则可以选择指定某个参数值

    基于期中成绩和期末成绩,按照指定的权重计算总评成绩

    >>> def my_sum(mid_score, end_score, mid_rate = 0.4):
    	score = mid_score*mid_rate + end_score*(1-mid_rate)
    	print(format(score,'.2f'))
    
    	
    >>> my_sum(80,90)
    86.00
    >>> my_sum(mid_score = 80,end_score = 90)
    86.00
    >>> my_sum(end_score = 90,mid_score = 80)
    86.00
    >>> 
    

    可变参数

    • 在声明函数时,可以通过带星号的参数(例如:def func(* param))向函数传递可变数量的实参,调用函数时,从那一点后所有的参数被收集为一个元组
    • 在声明函数时,可以通过带双星号的参数(例如:def func(** param))向函数传递可变数量的实参,调用函数时,从那一点后所有的参数被收集为一个字典

    利用带星的参数计算各数字的累加和

    >>> def my_sum(a,b,*c):
        total = a+b
        for i in c:
            total += i
        return total
    
    >>> print(my_sum(1,2))
    3
    >>> print(my_sum(1,2,3,4,5,6))
    21
    

    利用带星和带双星的参数计算各数字的累加和

    >>> def my_sum(a,b,*c,**d):
        total = a+b
        for i in c:
            total += i
        for key in d:
            total += d[key]
        return total
    
    >>> print(my_sum(1,2))
    3
    >>> print(my_sum(1,2,3,4))
    10
    >>> print(my_sum(1,2,3,4,male=1,female=2))
    13
    

    强制命名参数

    • 在带星号的参数后面声明参数会导致强制命名参数(Keyword-only),然后在调用时必须显式使用命名参数传递值
    • 因为按位置传递的参数默认收集为一个元组,传递给前面带星号的可变参数
    >>> def my_sum(*, mid_score, end_score, mid_rate = 0.4):
        score = mid_score*mid_rate + end_score*(1-mid_rate)
        print(format(score,'.2f'))
    
    >>> my_sum(mid_score=80,end_score=90)
    86.00
    >>> my_sum(end_score=90,mid_score=80)
    86.00
    >>> my_sum(80,90)
    Traceback (most recent call last):
      File "<pyshell#47>", line 1, in <module>
        my_sum(80,90)
    TypeError: my_sum() takes 0 positional arguments but 2 were given
    >>> 
    

    四:全局语句global示例,非局部语句nonlocal示例,输出局部变量和全局变量

    • 在函数体中可以引用全局变量,但是要为定义在函数外的全局变量赋值,需要使用global语句
    pi = 2.1415926
    e = 2.7182818
    
    def my_func():
        global pi
        pi = 3.14
        print("global pi = ", pi)
        e = 2.718
        print("local e = ", e)
    
    print('module pi = ', pi)
    print('module e = ', e)
    my_func()
    print('module pi = ', pi)
    print('module e = ', e)
    

    输出:

    module pi =  2.1415926
    module e =  2.7182818
    global pi =  3.14
    local e =  2.718
    module pi =  3.14
    module e =  2.7182818
    
    • 在函数体中可以定义嵌套函数,在嵌套函数中如果要为定义在上级函数体的局部变量赋值,可以使用nonlocal
    def outer_func():
        tax_rate = 0.17
        print('outer function tax rate is ',tax_rate)
        def inner_func():
            nonlocal tax_rate
            tax_rate = 0.01
            print('inner function tax rate is ',tax_rate)
        inner_func()
        print('outer function tax rate is ',tax_rate)
    
    outer_func()
    

    输出:

    outer function tax rate is  0.17
    inner function tax rate is  0.01
    outer function tax rate is  0.01
    
    • 输出局部变量和全局变量
    1. 内置函数locals(),局部变量列表
    2. 内置函数globals(),全局变量列表

    五:获取和设置最大递归数

    在sys模块中,函数getrecursionlimit()setrecursionlimit()用于获取和设置最大递归次数

    >>> import sys
    >>> sys.getrecursionlimit()
    1000
    >>> sys.setrecursionlimit(666)
    >>> sys.getrecursionlimit()
    666
    >>> 
    

    六:三个有趣的内置函数:eval()、exec()、compile()

    eval

    • 对动态表达式进行求值,返回值
    • eval(expression, globals=None, locals=None)
      expression是动态表达式的字符串
      globals和locals是求值时使用的上下文环境的全局变量和局部变量,如果不指定,则使用当前运行上下文
    >>> x = 2
    >>> str_func = input("请输入表达式:")
    请输入表达式:x**2+2*x+1
    >>> eval(str_func)
    9
    >>> 
    

    exec

    • 可以执行动态表达式,不返回值
    • exec(str, globals=None, locals=None)
    >>> exec("for i in range(10): print(i, end=' ')")
    0 1 2 3 4 5 6 7 8 9 
    >>> 
    

    compile

    • 编译代码为代码对象,可以提高效率
    • compile(source, filename, mode)
      source为代码语句的字符串;如果是多行语句,则每一行的结尾必须有换行符\n
      filename为包含代码的文件
      mode为编码方式,可以为'exec'(用于语句序列的执行),可以为'eval'(用于表达式求值),可以为'single'(用于单个交互语句)
    >>> co = compile("for i in range(10): print(i, end=' ')", '', 'exec')
    >>> exec(co)
    0 1 2 3 4 5 6 7 8 9 
    >>> 
    

    七:map(),filter()

    • map(f, iterable,…),将函数f应用于可迭代对象,返回结果为可迭代对象

    示例1:

    >>> def is_odd(x):
    	return x%2 == 1
    
    >>> list(map(is_odd,range(5)))
    [False, True, False, True, False]
    >>> 
    

    示例2:

    >>> list(map(abs,[1,-2,3,-4,5,-6]))
    [1, 2, 3, 4, 5, 6]
    >>> 
    

    示例3:

    >>> list(map(str,[1,2,3,4,5]))
    ['1', '2', '3', '4', '5']
    >>
    

    示例4:

    >>> def greater(x,y):
    	return x>y
    
    >>> list(map(greater,[1,5,7,3,9],[2,8,4,6,0]))
    [False, False, True, False, True]
    >>> 
    
    • filter(f, iterable),将函数f应用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素,返回结果为可迭代对象

    示例1(返回个位数的奇数):

    >>> def is_odd(x):
    	return x%2 == 1
    
    >>> list(filter(is_odd, range(10)))
    [1, 3, 5, 7, 9]
    >>> 
    

    示例2(返回三位数的回文):

    >>> list(filter(is_palindrome, range(100, 1000)))
    [101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]
    >>> 
    

    八:Lambda表达式和匿名函数

    匿名函数广泛应用于需要函数对象作为参数、函数比较简单并且只使用一次的场合

    格式:

    lambda arg1,arg2... : <expression>
    

    其中,arg1、arg2等为函数的参数,<expression>为函数的语句,其结果为函数的返回值

    示例1(计算两数之和):

    >>> f = lambda x,y : x+y
    >>> type(f)
    <class 'function'>
    >>> f(1,1)
    2
    >>> 
    

    示例2(返回奇数):

    >>> list(filter(lambda x:x%2==1, range(10)))
    [1, 3, 5, 7, 9]
    >>> 
    

    示例3(返回非空元素):

    >>> list(filter(lambda s:s and s.strip(), ['A', '', 'B', None, 'C', ' ']))
    ['A', 'B', 'C']
    >>> 
    

    补充:

    • strip()用来去除头尾字符、空白符(\n,\r,\t,’’,即换行、回车、制表、空格)
    • lstrip()用来去除开头字符、空白符
    • rstrip()用来去除结尾字符、空白符

    再补充一点:

    • \n到下一行的开头
    • \r回到这一行的开头

    示例4(返回大于0的元素):

    >>> list(filter(lambda x:x>0, [1,0,-2,8,5]))
    [1, 8, 5]
    >>> 
    

    示例5(返回元素的平方):

    >>> list(map(lambda x:x*x, range(10)))
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> 
    

    九:operator模块和操作符函数

    Python内置操作符的函数接口,它定义了对应算术和比较等操作的函数,用于map()、filter()等需要传递函数对象作为参数的场合,可以直接使用而不需要使用函数定义或者Lambda表达式,使得代码更加简洁

    示例1(concat(x,y)对应于x+y):

    >>> import operator
    >>> a = 'hello'
    >>>> operator.concat(a, ' world')
    'hello world'
    

    实例2(operator.gt对应于操作符>):

    >>> import operator
    >>> list(map(operator.gt, [1,5,7,3,9],[2,8,4,6,0]))
    [False, False, True, False, True]
    >>> 
    

    十:functools.reduce(),偏函数functools.partial(),sorted()

    functools.reduce()

    functools.reduce(func, iterable[, iterable[, initializer]])

    • 使用指定的带两个参数的函数func对一个数据集合的所有数据进行下列操作:
    • 使用第一个和第二个数据作为参数用func()函数运算,得到的结果再与第三个数据作为参数用func()函数运算,依此类推,最后得到一个结果
    • 可选的initialzer为初始值

    示例:

    >>> import functools,operator
    >>> functools.reduce(operator.add, [1,2,3,4,5])
    15
    >>> functools.reduce(operator.add, [1,2,3,4,5], 10)
    25
    >>> functools.reduce(operator.add, range(1,101))
    5050
    >>> 
    >>> functools.reduce(operator.mul, range(1,11))
    3628800
    

    偏函数functools.partial()

    functools.partial(func, *arg, **keywords)

    • 通过把一个函数的部分参数设置为默认值的方式返回一个新的可调用(callable)的partial对象
    • 主要用于设置预先已知的参数,从而减少调用时传递参数的个数

    示例(2的n次方):

    >>> import functools,math
    >>> pow2 = functools.partial(math.pow, 2)
    >>> list(map(pow2, range(11)))
    [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0]
    >>> 
    

    十一:sorted()

    sorted(iterable, *, key=None, reverse=False)

    • iterable是待排序的可迭代对象
    • key是比较函数(默认为None,按自然顺序排序)
    • reverse用于指定是否逆序排序

    示例1(数值。默认自然排序):

    >>> sorted([1,6,4,-2,9])
    [-2, 1, 4, 6, 9]
    >>> sorted([1,6,4,-2,9], reverse=True)
    [9, 6, 4, 1, -2]
    >>> sorted([1,6,4,-2,9], key=abs)
    [1, -2, 4, 6, 9]
    

    示例2(字符串,默认按字符串字典序排序):

    >>> sorted(['Dod', 'cat', 'Rabbit'])
    ['Dod', 'Rabbit', 'cat']
    >>> sorted(['Dod', 'cat', 'Rabbit'], key=str.lower)
    ['cat', 'Dod', 'Rabbit']
    >>> sorted(['Dod', 'cat', 'Rabbit'], key=len)
    ['Dod', 'cat', 'Rabbit']
    

    示例3(元组,默认按元组的第一个元素排序):

    >>> sorted([('Bob', 75), ('Adam', 92), ('Lisa', 88)])
    [('Adam', 92), ('Bob', 75), ('Lisa', 88)]
    >>> sorted([('Bob', 75), ('Adam', 92), ('Lisa', 88)], key=lambda t:t[1])
    [('Bob', 75), ('Lisa', 88), ('Adam', 92)]
    

    十二:函数装饰器

    这玩意就很有意思了,很Java语言中的注解是很相像的

    示例1:

    import time,functools
    
    def timeit(func):
        def wrapper(*s):
            start = time.perf_counter()
            func(*s)
            end = time.perf_counter()
            print('运行时间:', end - start)
        return wrapper
    
    @timeit
    def my_sum(n):
        sum = 0
        for i in range(n): sum += i
        print(sum)
    
    if __name__ == '__main__':
        my_sum(10_0000)
    

    结果:

    4999950000
    运行时间: 0.013929100000000028
    

    怎么理解上面的代码呢?

    • 首先,timeit()返回的是wrapper,而不是执行(没有小括号)
    • @timeit相当于,在调用my_sum()的前一刻,会执行这么个语句:my_sum = timeit(my_sum)

    示例2:

    def makebold(fn):
        def wrapper(*s):
            return "<b>" + fn(*s) + "</b>"
        return wrapper
    
    def makeitalic(fn):
        def wrapper(*s):
            return "<i>" + fn(*s) + "</i>"
        return wrapper
    
    @makebold
    @makeitalic
    def htmltags(str1):
        return str1
    
    print(htmltags('Hello'))
    
    

    输出:

    <b><i>Hello</i></b>
    

    选择题:1~5

    1

    >>> print(type(lambda:None))
    <class 'function'>
    

    2

    >>> f = lambda x,y:x*y
    >>> f(12, 34)
    408
    

    3

    >>> f1 = lambda x:x*2
    >>> f2 = lambda x:x**2
    >>> print(f1(f2(2)))
    8
    

    4

    >>> def f1(p, **p2):
    	print(type(p2))
    
    	
    >>> f1(1, a=2)
    <class 'dict'>
    

    5

    >>> def f1(a,b,c):
    	print(a+b)
    
    	
    >>> nums = (1,2,3)
    >>> f1(*nums)
    3
    

    思考题:4~11

    4

    >>> d = lambda p:p*2
    >>> t = lambda p:p*3
    >>> x = 2
    >>> x = d(x)
    >>> x = t(x)
    >>> x = d(x)
    >>> print(x)
    24
    

    5

    >>> i = map(lambda x:x**2, (1,2,3))
    >>> for t in i:
    	print(t, end=' ')
    
    	
    1 4 9 
    

    6

    >>> def f1():
    	"simple function"
    	pass
    
    >>> print(f1.__doc__)
    simple function
    

    7

    >>> counter = 1
    >>> num = 0
    >>> def TestVariable():
    	global counter
    	for i in (1, 2, 3) : counter += 1
    	num = 10
    
    	
    >>> TestVariable()
    >>> print(counter, num)
    4 0
    

    8

    >>> def f(a,b):
    	if b==0 : print(a)
    	else : f(b, a%b)
    
    	
    >>> print(f(9,6))
    3
    None
    

    求最大公约数

    9

    >>> def aFunction():
    	"The quick brown fox"
    	return 1
    
    >>> print(aFunction.__doc__[4:9])
    quick
    

    10

    >>> def judge(param1, *param2):
    	print(type(param2))
    	print(param2)
    
    	
    >>> judge(1, 2, 3, 4, 5)
    <class 'tuple'>
    (2, 3, 4, 5)
    

    11

    >>> def judge(param1, **param2):
    	print(type(param2))
    	print(param2)
    
    	
    >>> judge(1, a=2, b=3, c=4, d=5)
    <class 'dict'>
    {'a': 2, 'b': 3, 'c': 4, 'd': 5}
    

    上机实践:2~5

    2. 编写程序,定义一个求阶乘的函数fact(n),并编写测试代码,要求输入整数n(n>=0)。请分别使用递归和非递归方式实现

    递归方式:

    def fact(n):
        if n == 0 :
            return 1
        return n*fact(n-1)
    
    n = int(input("请输入整数n(n>=0):"))
    print(str(n)+" ! =  " + str(fact(n)))
    
    

    非递归方式:

    def fact(n):
        t = 1
        for i in range(1,n+1):
            t *= i
        return t
    
    n = int(input("请输入整数n(n>=0):"))
    print(str(n)+" ! =  " + str(fact(n)))
    
    

    输出:

    请输入整数n(n>=0):5
    5 ! =  120
    

    3. 编写程序,定义一个求Fibonacci数列的函数fib(n),并编写测试代码,输出前20项(每项宽度5个字符位置,右对齐),每行输出10个。请分别使用递归和非递归方式实现

    递归方式:

    def fib(n):
        if (n == 1 or n == 2):
            return 1
        return fib(n-1)+fib(n-2)
    
    for i in range(1,21):
        print(str(fib(i)).rjust(5,' '),end = ' ')
        if i %10 == 0:
            print()
    

    非递归方式:

    def fib(n):
        if (n == 1 or n == 2):
            return 1
        n1 = n2 = 1
        for i  in range(3,n+1):
            n3 = n1+n2
            n1 = n2
            n2 = n3
        return n3
    
    for i in range(1,21):
        print(str(fib(i)).rjust(5,' '),end = ' ')
        if i %10 == 0:
            print()
    

    输出:

        1     1     2     3     5     8    13    21    34    55
       89   144   233   377   610   987  1597  2584  4181  6765
    

    4. 编写程序,利用可变参数定义一个求任意个数数值的最小值的函数min_n(a,b,*c),并编写测试代码。例如对于“print(min_n(8, 2))”以及“print(min_n(16, 1, 7, 4, 15))”的测试代码

    def min_n(a,b,*c):
        min_number = a if(a < b) else b
        for n in c:
            if n < min_number:
                min_number = n
        return min_number
    
    print(min_n(8, 2))
    print(min_n(16, 1, 7, 4, 15))
    

    输出:

    2
    1
    

    5. 编写程序,利用元组作为函数的返回值,求序列类型中的最大值、最小值和元素个数,并编写测试代码,假设测试代码数据分别为s1=[9, 7, 8, 3, 2, 1, 55, 6]、s2=[“apple”, “pear”, “melon”, “kiwi”]和s3="TheQuickBrownFox"

    def func(n):
        return (max(n),min(n),len(n))
        
    s1=[9, 7, 8, 3, 2, 1, 55, 6]
    s2=["apple", "pear", "melon", "kiwi"]
    s3="TheQuickBrownFox"
    
    for i in (s1,s2,s3):
        print("list = ", i)
        t = func(i)
        print("最大值 = {0},最小值 = {1},元素个数 = {2}".format(t[0], t[1], t[2]))
    

    输出:

    list =  [9, 7, 8, 3, 2, 1, 55, 6]
    最大值 = 55,最小值 = 1,元素个数 = 8
    list =  ['apple', 'pear', 'melon', 'kiwi']
    最大值 = pear,最小值 = apple,元素个数 = 4
    list =  TheQuickBrownFox
    最大值 = x,最小值 = B,元素个数 = 16
    

    案例研究:井字棋游戏

    https://blog.csdn.net/Zhangguohao666/article/details/103280740

    了解Python函数的定义和使用


    由于本文的内容太多了,导致了两个很不好的结果,
    一是:在网页中打开本篇博客的加载时间太长了,明显的卡顿很影响阅读体验;
    二是:本人在对本篇文章进行更新或者修改内容时,卡的要死。
    遂,
    将本文第八章后面的很多内容拆分到新的文章中,望大家理解


    第九章 面向对象的程序设计


    第十章 模块和客户端


    第十一章 算法与数据结构基础


    第十二章 图形用户界面


    我对图形用户界面基本无兴趣,无特殊情况,基本不打算碰这方面内容

    案例研究:简易图形用户界面计算器

    第十三章 图形绘制


    与上一章相同,我对于图形绘制的兴趣也基本没有,尝试做了2-7题,就完全没兴趣做下去了

    图形绘制模块:tkinter

    2. 参考例13.2利用Canvas组件创建绘制矩形的程序,尝试改变矩形边框颜色以及填充颜色

    from tkinter import *
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = 130, height = 70)
    c.pack()
    
    c.create_rectangle(10, 10, 60, 60, fill = 'red')
    c.create_rectangle(70, 10, 120, 60, fill = 'green', outline = 'blue', width = 5)
    
    

    创建画布对象:

    • root = Tk()
      创建一个Tk根窗口组件root
    • c = Canvas(root, bg = 'white', width = 130, height = 70)
      创建大小为200 * 100、背景颜色为白色的画布
    • c.pack()
      调用组件pack()方法,调整其显示位置和大小

    绘制矩形:

    c.create_rectangle(x0, y0, x1, y1, option, ...)
    
    • (x0,y0)是左上角的坐标
    • (x1,y1)是右下角的坐标
    • c.create_rectangle(70, 10, 120, 60, fill = 'green', outline = 'blue', width = 5)
      用蓝色边框、绿色填充矩形,边框宽度为5

    3. 参考例13.3利用Canvas组件创建绘制椭圆的程序,尝试修改椭圆边框样式、边框颜色以及填充颜色

    from tkinter import *
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = 280, height = 70)
    c.pack()
    
    c.create_oval(10, 10, 60, 60, fill = 'green')
    c.create_oval(70, 10, 120, 60, fill = 'green', outline = 'red', width = 5)
    c.create_oval(130, 25, 180, 45, dash = (10,))
    c.create_oval(190, 10, 270, 50, dash = (1,), width = 2)
    
    

    绘制椭圆

    c.create_oval(x0, y0, x1, y1, option, ...)
    
    • (x0,y0)是左上角的坐标
    • (x1,y1)是右下角的坐标
    • c.create_oval(70, 10, 120, 60, fill = 'green', outline = 'red', width = 5)
      绿色填充、红色边框,宽度为5
    • c.create_oval(130, 25, 180, 45, dash = (10,))
      虚线椭圆

    4. 参考例13.4利用Canvas组件创建绘制圆弧的程序,尝试修改圆弧样式、边框颜色以及填充颜色

    from tkinter import *
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = 250, height = 70)
    c.pack()
    
    c.create_arc(10, 10, 60, 60, style = ARC)
    c.create_arc(70, 10, 120, 60, style = CHORD)
    c.create_arc(130, 10, 180, 60, style = PIESLICE)
    for i in range(0, 360, 60):
        c.create_arc(190, 10, 240, 60, fill = 'green', outline = 'red', start = i, extent = 30)
    
    

    绘制圆弧:

    c.create_arc(x0, y0, x1, y1, option, ...)
    
    • (x0,y0)是左上角的坐标
    • (x1,y1)是右下角的坐标
    • 选项start(开始角度,默认为0)和extend(圆弧角度,从start开始逆时针旋转,默认为90度)决定圆弧的角度范围
    • 选项start用于设置圆弧的样式

    5. 参考例13.5利用Canvas组件创建绘制线条的程序,尝试修改线条样式和颜色

    from tkinter import *
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = 250, height = 70)
    c.pack()
    
    c.create_line(10, 10, 60, 60, arrow = BOTH, arrowshape = (3, 4, 5))
    c.create_line(70, 10, 95, 10, 120, 60, fill = 'red')
    c.create_line(130, 10, 180, 10, 130, 60, 180, 60, fill = 'green', width = 10, arrow = BOTH, joinstyle = MITER)
    c.create_line(190, 10, 240, 10, 190, 60, 240, 60, width = 10)
    
    

    绘制线条:

    c.create_line(x0, y0, x1, y1, ..., xn, yn, option, ...)
    
    • (x0,y0),(x1,y1),…,(xn,yn)是线条上各个点的坐标

    6. 参考例13.6利用Canvas组件创建绘制多边形的程序,尝试修改多边形的形状、线条样式和填充颜色

    from tkinter import *
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = 250, height = 70)
    c.pack()
    
    c.create_polygon(35, 10, 10, 60, 60, 60, fill = 'red', outline = 'green')
    c.create_polygon(70, 10, 120, 10, 120, 60, fill = 'white', outline = 'blue')
    c.create_polygon(130, 10, 180, 10, 180, 60, 130, 60, outline = 'blue')
    c.create_polygon(190, 10, 240, 10, 190, 60, 240, 60, fill = 'white', outline = 'black')
    
    

    绘制多边形:

    c.create_polygon(x0, y0, x1, y1, ..., option, ...)
    
    • (x0,y0),(x1,y1),…,(xn,yn)是多边形上各个顶点的坐标

    7. 参考例13.7利用Canvas组件创建绘制字符串和图形的程序,绘制y = cos(x) 的图形

    绘制字符串:

    c.create_text(x, y, option, ...)
    
    • (x,y)是字符串放置的中心位置

    y = sin(x)

    from tkinter import *
    import math
    
    WIDTH, HEIGHT = 510, 210
    ORIGIN_X, ORIGIN_Y = 2, HEIGHT/2 #原点
    
    SCALE_X, SCALE_Y = 40, 100 #x轴、y轴缩放倍数
    ox, oy = 0, 0
    x, y = 0, 0
    arc = 0 #弧度
    END_ARC = 360 * 2 #函数图形画两个周期
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = WIDTH, height = HEIGHT)
    c.pack()
    
    c.create_text(200, 20, text = 'y = sin(x)')
    c.create_line(0, ORIGIN_Y, WIDTH, ORIGIN_Y) 
    c.create_line(ORIGIN_X, 0, ORIGIN_X, HEIGHT) #绘制x轴,y轴
    for i in range(0, END_ARC+1, 10):
        arc = math.pi * i / 180
        x = ORIGIN_X + arc * SCALE_X
        y = ORIGIN_Y - math.sin(arc) * SCALE_Y
        c.create_line(ox, oy, x, y)
        ox, oy = x, y
    

    y = cos(x)

    from tkinter import *
    import math
    
    WIDTH, HEIGHT = 510, 210
    ORIGIN_X, ORIGIN_Y = 2, HEIGHT/2 #原点 
    
    SCALE_X, SCALE_Y = 40, 100 #x轴、y轴缩放倍数
    ox, oy = 0, 0
    x, y = 0, 0
    arc = 0 #弧度
    END_ARC = 360 * 2 #函数图形画两个周期
    
    root = Tk()
    c = Canvas(root, bg = 'white', width = WIDTH, height = HEIGHT)
    c.pack()
    
    c.create_text(200, 20, text = 'y = cos(x)')
    c.create_line(0, ORIGIN_Y, WIDTH, ORIGIN_Y) 
    c.create_line(ORIGIN_X, 0, ORIGIN_X, HEIGHT) 
    for i in range(0, END_ARC+1, 10):
        arc = math.pi * i / 180 
        x = ORIGIN_X + arc * SCALE_X
        y = ORIGIN_Y - math.cos(arc) * SCALE_Y
        c.create_line(ox, oy, x, y)
        ox, oy = x, y
    
    
    

    图形绘制模块:turtle


    后面章节内容:未完待续…

    第十四章 数值日期和时间处理


    第十五章 字符串和文本处理


    第十六章 文件和数据交换


    第十七章 数据访问


    第十八章 网络编程和通信


    第十九章 并行计算:进程、线程和协程


    第二十章 系统管理

    展开全文
  • 基于物品的协同过滤算法实现图书推荐系统

    万次阅读 多人点赞 2019-09-14 21:20:24
    本文首先介绍了推荐系统的发展历史,及目前常用的几种推荐算法的介绍与比较,然后以基于物品的协同过滤算法为基础,详细介绍图书推荐系统的构建。在该系统中,主要功能分为用户功能和图书推荐功能...

    点击可免费下载原文噢

    摘 要

    在当下这个信息爆炸的时代,各种各样的书籍条目繁多,浩如烟海;相应地,为满足用户需求,电商平台需要推荐系统来帮助用户找到自己可能需要的书籍。本文旨在利用基于物品的协同过滤算法,来实现一个图书推荐系统。
    本文首先介绍了推荐系统的发展历史,及目前常用的几种推荐算法的介绍与比较,然后以基于物品的协同过滤算法为基础,详细介绍图书推荐系统的构建。在该系统中,主要功能分为用户功能和图书推荐功能。用户功能包括用户账号的登录与注册,书籍查询,书籍评分。图书推荐功能利用基于物品的协同过滤算法,先计算各个书籍之间的相似度,再根据物品相似度和用户的行为数据计算用户对各个书籍的兴趣度,从而得出推荐结果。
    该系统数据库采用MySQL,采用python编程工具Pycharm编写。

    关键词:电子商务;推荐系统;个性化图书推荐;协同过滤;基于物品。

    ABSTRACT

    In this era with information explosion,there are so many kinds of books. In accordance, to meet user needs, e-commerce platforms need a recommendation system to help users find books they might need. This paper aims to implement a book recommendation system, using an item-based collaborative filtering algorithm.
    This paper first introduces the development history of the recommendation system, and make introduction and comparison among several recommended algorithms which is often used. Then based on the item-based collaborative filtering algorithm, the construction of the book recommendation system is introduced in detail. In this system, the main functions include user functions and book recommendation functions. User functions include login and registration of user accounts, book query and book rating. The book recommendation function uses the item-based collaborative filtering algorithm to first calculate the similarity between the books, and then calculates the user’s interest in each book according to the similarity of the items and the behavior data of the user, thereby obtaining the recommendation result.
    Development tools include MySQL and Pycharm.

    Key words:E-commerce; recommendation system; personalized book recommendation; collaborative filtering; item-based.

    第一章 概述

    1.1课题背景及意义

    随着计算机信息技术和互联网技术的发展,从之前的信息短缺时代,跨越到了信息过剩时代。在这种背景下,人们越来越难以从许多信息中找到感兴趣的信息。相对来说,对于信息来说,想要找到对自身感兴趣的用户也越来越难。而本文研究的推荐系统任务,就是将信息与用户连接。
    想象一下,用户想要购买一本书,例如《C Prime Plus》。用户只需走进书店并按照书名直接购买即可。也可以通过淘宝、京东、当当直接搜索,进行购买。不过,这种方式的前提是用户需要明确自己的需求,确切地知道自己想买的哪本书。
    但是,如果用户没有明确的目标,比如寻找自己喜欢的音乐,用户可以使用预先定义的类型或标签搜索有趣的音乐,但面对大量的音乐,实际上用户可以找到自己感兴趣的音乐。在这个时候,需要分析用户已收听音乐自动工具,用户有兴趣向用户推荐音乐。这是个性化推荐系统的工作[1]。
    信息过滤系统具有以下两个特点:
    1)主动性。从用户的角度来看,门户站点和搜索引擎是解决信息过载的有效手段,但它们需要提供明确需求的用户。如果用户不能正确地说明自己的需要,则这两种方法不能为用户提供正确的服务。用户不需要提供特定的需求,但信息可以由用户推荐。
    2)个性化。推荐系统的核心内容是找到长尾信息[2]。销路好的商品一般表示大多数用户的兴趣,而冷门商品一般表示少数用户的个性需求。在电子商务平台的时代,冷门商品的交付甚至超过了爆品。长尾信息的发现是推荐系统的重要研究方向。
    现在,推荐系统的思想和算法已经趋于成熟,在很多领域被广泛应用,最普遍的是电子商务。同时,随着机器学习和深入学习的发展,工业界和学术界热衷于研究这一挑战性的学科体系。

    1.2推荐系统的发展历史

    推荐系统是一个先进的思想,所以具有其独特性,因为它是一种仅属于网络时代的个性化信息检索工具。随着互联网和大数据时代的到来,人们逐渐意识到,所有信息的不定向推广是耗时费力且收效甚微的,这就体现出来推荐系统的价值。经过20多年的积累和沉淀,它逐渐成为一个独立的问题。
    1994年,明尼苏达集团透镜研究小组推出了第一个自动推荐系统, GroupLens。提出协同过滤是推荐系统中的一项重要技术。
    推荐系统(recommendersystem,RS)于1997年提出。由于推荐系统一词的广泛应用,推荐系统成为一个重要的研究领域。
    1998年,Amazon.com推出了一种基于项目的协作过滤算法。
    2003年,Amazon Linden等人本文提出了一种基于物品的协同过滤算法。据统计,推荐系统的贡献率在20%到30%之间。
    2005年,Admavicius等人论文分为三大类:基于内容的推荐、基于协同过滤的推荐和混合推荐,并提出了今后的研究方向。
    2006年10月,北美在线视频服务提供商Netflix,举办了一个比赛,在学术界和工业界引起了相当大的关注。奖项丰厚,与会者提出了几种推荐算法,以提高推荐的准确性,极大地促进了推荐系统的发展[3]。
    2007年在美国举行的第一次ACM推荐系统会议是2017年第11次。这是推荐系统领域的顶级会议。它提供了一个重要的国际论坛,展示不同领域推荐系统的最新研究成果、方法和方法。
    在2016年,YouTube的宣布使用推荐系统深层神经网络来获得大规模的建议最有可能的建议。
    近年来,推荐系统已广泛应用于电子商务推荐、广告定向投放、时事新闻推荐、抖音、知乎等平台。

    1.3推荐系统的研究内容

    经过20多年的贮存和沉淀,推荐系统在许多领域的应用取得了成功。最常见的应用场景是电商、广告、视频、社交和音乐。这些应用及需要通过推荐系统进行进一步的发展,所以这也是推荐系统研究和应用的重要实验场景。
    随着推荐系统的发展,用户逐渐接受了这种模式,经过调研,用户目前不仅对模型用户历史行为的分析感到满意,而且认可了混合推荐模型。各行业的应用都在致力于通过不同的推荐方法解决冷启动和非常稀疏的数据问题。当前,中国著名新闻客户的头条新闻使用内容分析、用户标签、评级分析等方法创造了数百万美元。用户推荐引擎发展迅猛。
    移动互联网的普及为移动电子商务数据、移动社会数据和地理数据等推荐系统提供了更多的数据。它成为社会推荐的新的尝试。
    通过对推荐系统的应用,推荐系统的有效性评估,稳健性和安全性的算法进行了研究。在2015年,艾伦说和其他人在雷克斯会议上发言。同年,Frank Hopfgartner等人讨论了基于流数据和比较实验的离线评估方法,并进行宣布。
    近年来,机器学习和深度学习的发展为推荐系统提供了方法论指导。2016年以后,RECSYS会议召开了关于推荐体系的深入学习研讨会,推动了研究,鼓励在深入学习的基础上应用推荐体系[4]。
    2017年,Alexandros Karatzoglou等人他的论文介绍了推荐系统的深度学习应用。

    第二章 开发平台及技术

    2.1开发平台

    2.1.1系统开发环境介绍

    Python是一种目前广泛使用的语言,非常受使用者们的欢迎,因其自身的优越性很快就得到了迅速的发展。这对C++,java等今年来流行的语言造成了很强的影响。拥有良好的通用性,作业迅速,良好的跨平台和稳性是Python技术的优点,目前,像个人电脑、数据操作、电脑和手机游戏后台、手机移动端和计算机应用的很多方面都在使用Python作为开发语言,技术稳定成熟,缩短开发时间,重复性好,在线扩展方便。Python因风格简洁、可读性较好深受编程人员的喜爱,并得到了广泛运用[1]。对于推荐系统来说,开发过程中会遇到各种各样的问题,所以在编写程序和运行代码的过程中,关于设计的逻辑和设计过程,都是一个不断发现问题、解决问题的过程,不断完善以达到预期的功能才是我们希望看到的。Python语言具有其他语言所没有的特性,可以使用它进行开发本图书推荐系统平台。
    Python的多样性,意味着可以横跨多个领域,绝不仅限于Web开发、桌面程序、移动应用,甚至包含硬件开发等。所以并没有被束缚在单一的平台之上,Python具有良好的可移植性,在图书推荐系统开发上使用Python可以大大方便项目的开发和维护。
    由上面论述可知,通过Pygame工具的应用,可以简化项目的开发,Pygame使开发者不必过多的在意一些琐碎的问题,因为这些问题Pygame工具会帮我们解决,这让开发者拥有更多的时间和精力放在系统开发的关键部分,给开发者带来了相当大的便利。最关键的是,Pygame拥有跨平台的特性,这使得基于它开发的项目可以自由的在各个操作系统上运行,这省去了很多繁琐的修改。随着Pygame开发工具的流行,该工具以其独特的优越性,越来越多的被应用于各种系统平台的开发。
    当前程序是以python为编程语言,主要功能实现依赖于pygame模块,主要用到surface对象之间的位置变化,再利用事件监听让程序运行起来。运行中Surface对象的位置发生变化后,界面刷新,用户对鼠标与键盘进行操作时,监听操作完成相应事件。
    软件开发方式:①系统总体设计②系统详细设计③编码④测试。
    系统运行环境:Windows7及更高版本。

    2.1.2 数据库系统介绍

    MySQL是一个开源的关系数据库管理系统(RDBMS),它使用广泛使用的结构化语言(SQL)进行数据库管理。
    MySQL是一个开源的,因此任何人都可以在通用公共许可证下下载并更改设置以满足个人需求。
    MySQL速度快、可靠性高、适应性强,一直受到人们的广泛关注。大多数人认为MySQL是在不进行事务处理的情况下管理内容的最佳选择。
    因此,对于简单的数据库使用需求,我选择MySQL作为数据库管理工具。

    2.1.3 开发工具介绍

    PyCharm是一种Python 集成开发环境,它有一套工具可以帮助用户提高开发python语言的效率,例如调试、语法突出显示、智能提示、自动作业、单元测试、project管理、代码跳跃、版本控制。
    编码支持:其支持智能化的 、可配置的编辑器提供代码完成、代码片段、代码折叠和窗口拆分支持,使用户快速便捷的完成任务,节省了用户的时间。
    项目代码导航器:这个IDE快速的帮助引领用户在文件之间切换,从一种方式浏览语句、用法和类的层次结构。如果用户记得使用系统默认提供的快捷键或者是他们自己设置的快捷键,使用效率会更高。
    代码分析:用户可以使用编码规则、错误突出显示、智能检测和一键代码快速完成建议来优化编码[5]。
    Python重构:在程序编写过程中,导入域/变量/常量,重命名,提取方法/超类,移动和前推/后退重构这些操作可以使用这个功能来实现,极大的减少了用户的任务量。
    集成版本控制:如果用户想使用这些功能,如登录、输入、视图拆分和合并, -用户可以在VCS用户界面中找到,这是其通用的功能。
    具有自带的调试器,调试器的功能多样化,可以提供多种功能,用户通过对基于python和 Django的项目进行调试,同样,系统的单元测试,也可以通过它来解决,该调试器包括blake点、分步、多屏幕视图、窗口和计算表达式等。
    集成单元测试:用户可以运行测试文件,单个测试类。一个方法或者所有测试项目。
    另一方面,Pycharco还为Django的开发提供了一些很好的功能,以及对Google应用引擎的支持,以及对Pycharm的支持。

    2.2 开发技术

    python用作该软件的开发语言,其关键技术在于布局、事件监控和数据存储。布局主要是美化界面以及界面的排版。用户交互界面用布局来实现,给用户带来美观、舒适、直接的用户体验。所以,界面布局的设计会影响到使用者的客观感受,获取用户操作使用事件监视技术,而数据监视技术记录所有操作,创建用户需要提取的数据。
    Python是一种优雅、简单、健壮的开源解释语言。产生于1989年,由Givavo RSM开发和设计,设计Pyhlo的最初目的是为了高效的完成某一项任务而创造的。它从一种为提高研究项目的工作效率而创建的通用编程语言开始。经过多年的发展,python已经逐渐得到改进。由于其强大的可扩展性和广泛的库支持,它已经出现在许多领域,如豆瓣等就是成功的应用python技术的例子。
    Python的主要特点有:
    l)低入门标准的python语法相当简短,编写的程序通常简短,非常像日常使用的自然语言,有利于开发者的使用和理解。
    2)Python是一门面向对象的语言,在面向对象中,与面向过程语言的差距就是类和对象的使用,体现了python的特征,面向对象的语言特点是在该语言被创造的时候就体现的。Python之所以能成为一门被大众喜爱的编程语言在于它的精心设计的数据和内存管理。
    3)对于内存的管理,python也有其特定的部分负责,对于开发者来说,程序就显得有必要了,因为开发者只有理解这些程序的前后逻辑才能更好的写出项目,使他们不像C/C++程序员那样专注于处理内存事务。Python的程序设计和编写时间更短、出错更少也是基于此特性。
    4)主机语言与其通信可以方便的被嵌入,可以用C语言编写对于一些对性能特别强调的地方,这些扩展在python中被调用以实现性能改进的目的。相反,Python解释器可以嵌入到C/C++中,它取代接口可以通过动态链接库的形式进行,通过这种方式,程序开发由此变得灵活方便[7]。
    5)在python的标准库中,含有多个模块来实现具体的功能,这些几乎包含了所有与操作系统解释器的交互的功能,也就是Python使用者不用手动人工造轮子,因为这些模块可以直接用于已经完全测试过的功能开发。这些已经被充分测试的模版在实际的编程开发中得到了充分的应用。

    2.3 关键算法

    2.3.1 常见的推荐算法

    现如今网上信息泛滥,想要在里面找一条适合自己的信息的成本真的有点高,如果可以较为完善的推荐系统出现的话,于用户而言,可以大大的节省自己的时间;从商家的角度来看,通过推荐系统可以更为精准地投放自己的商品对象,从而可以更好的卖出自己的商品。
    根据使用数据源的不同可将其大致分为三类:
    1)协同过滤的推荐方法
    2)基于内容的推荐方法
    3)基于知识的推荐方法
    这三类算法通过不同程度融合,可以出现混合推荐算法。
    一、协同过滤算法,其中包括基于用户的协同过滤及基于物品的协同过滤。
    1)基于用户的实现原理:
    1.计算用户之间的距离
    2.将用户之间相近的,推荐给他们喜欢的物料
    3.通过收集用户反馈数据,进一步优化用户之间的距离
    2)基于物品的实现原理:
    1.计算物品之间的相似度矩阵
    2.收集用户评分高的物物品
    3.将与用户评分高的相似度较高的物品,推荐给用户
    4.通过收集用户反馈数据,进一步优化数据
    二、基于内容推荐算法
    实现原理:建立用户画像-行为偏好,建立物品画像,特征,通过相似度计算,然后推荐。
    弊端:建立用户画像,需要基于大量用户行为数据。
    三、基于知识推荐算法
    实现原理:基于知识的推荐算法主要将重点放在知识源,没有冷启动的问题,因为推荐的需求都是被直接引出的。其主动的询问用户的需求,然后返回推荐结果。
    弊端:“知识”的获取比较难。

    2.3.2基于物品的协同过滤算法

    协同过滤推荐算法是推荐系统中最基本的算法。它分为基于用户的协同过滤算法(usercf)和基于物品的协同过滤算法(itemcf)。
    基于物品的协同过滤算法主要分为两个步骤。
    1)计算物品之间的相似性。
    2)根据物品相似度与用户历史行为的,向用户提供推荐列表。
    第一步骤中的关键点是计算项之间的相似度。除了使用基于内容的相似性,它是计算有多少类似的物品,而是看喜欢i的用户中,有多少人喜欢j的,因此计算是基于用户。该兴趣一般都比较确定和不容易改变。当一个用户都喜欢的物品,我们通常可以认为,这两个物品可能属于同一类别。令N(i)表示购买物品i的用户数,则物品i和物品j的相似度可以用公式1来计算。
    ,(1)
    第一步的时间复杂度的改进方法:以UserCF类似,我们可以创建一个用户,项目查找表,通过计算,认为用户有beenhave这些项目之间的相似性时,它可以保证计算的相似性。这样能够保证相似度是有用的,而不用对那些零(可靠地稀疏矩阵)花费大量的计算量。
    第一步相似的改进方法1:如果按上述公式计算的相似性,可以发现,这个物品我和受欢迎的物品j之间的相似性是非常高的,因为流行的读数偏高,所以基本上每个人都会买它。具有较高的知名度的商品不太区分的,所以我们需要惩罚流行物品j的权重[10]。
    ,(2)
    第一步相似性改进方法2:需要惩罚用户的活动。如果用户不活跃,只有购买的图书数量有限,那么这些书很可能在计算项目中感兴趣的一个或两个区域的相似性是有益的,但如果一个书店卖家提供折扣,如果你买90%Amazon的书籍,然后赚取差价,那么用户的行为对计算物品的相似性不会有任何作用,因为90%的书肯定会涵盖了很多的范围,所以你应该惩罚用户的活动,可以采取第一个方法。
    第一步相似性改进方法3:物品的相似性的归属。规范化不仅提高了建议的准确性,还增加了建议的覆盖范围和多样性。例如,在京东上,用户的爱好种类繁多,有相机爱好者、耳机爱好者、电脑爱好者等。很少有人说爱好集中在一个类别中。假设有两种类型的A和B.A类之间的相似性是0.5,B类之间的相似性是0.8,A和B之间的相似性是0.2。当用户购买A类的5本书和B类的5本书后,我们必须向用户提供推荐。如果我们按照前面的方法并按相似性排序,那么推荐的方法应该是B类项目。即使B类别较低,它仍然优于A类。为了高相似性,所以相似性的相似性应该基于类别,因此A的相似性为1,B的相似性也为1 ,以便排序后推荐的A和B产品具有更高的准确性,覆盖范围和多样性。
    第二步则比较简单,计算物品与用户已买物品的相似度(权重和),然后根据相似度排序选出topN。
    ItemCF在实际系统中运用的比较多,主要有两个优点:
    1)item-item表相比如user-user表要小的多,处理起来比较容易
    2)itemcf很容易为推荐提供理由,比如提前进行数据挖掘,可提高可靠性,改善用户与推荐系统的相互作用,并进一步加强定制推荐前推荐数据挖掘等。
    基于物品的协同过滤算法与用户的协同过滤算法相比,基于用户的协同过滤算法有两大缺点。
    1)随着网站用户数量的增加,计算用户数量的相似性就更加困难了。计算的时间复杂度和空间复杂度与用户的增长基本成平方关系。
    2)基于用户的合作过滤算法很难对推荐的结果作出解释和建议。
    基于物品的协作过滤算法是“目标用户”,用于查找与其喜欢的项目类似的项目。从实际情况角度出发,在对于本课题中的需求——图书推荐来说,每个用户对于个性化推荐书籍的需求都比较强烈,此时采用基于物品的协同过滤算法就可以更为充分地挖掘用户的兴趣领域,并且很容易根据用户的历史数据来对推荐结果做出解释,从而使用户更加信任系统做出的推荐结果。

    第三章 系统设计

    3.1 需求分析与建模

    需求分析是软件工程中的一个重要步骤。这个阶段的主要任务就是调查用户需求。并和开发人员进行确认,将客户的非技术性需求转化为技术上可实现的技术性需求[12][13]。明确要实现哪些功能、完成哪些工作,产出规范性文档《需求规格说明书》。在通过评审后,《需求规格说明书》起到了桥梁的作用,成为用户、开发人员进行理解与交流、反映用户的问题结构以用作软件开发的工作依据、作为软件测试和验收的依据[14]。
    总体设计原则的提出是为了确保系统建设成功,并为系统的可持续发展做出规划。因此在系统设计时,我们遵守以下原则:
    简单性:实现系统需求的前提下,尽可能的保证系统简单易操作。一方面,简单的操作会使增进用户体验,另一方面操作过于复杂时,更易引入问题及漏洞。
    针对性:本项目是选用基于物品的协同过滤算法实现图书推荐系统,用明确项目需求为目标,具体需求具体实现,有很强的针对性。
    实用性和一致性:具有较高的视觉一致性。主界面采用tkinter模块进行编写,主界面上的元素统一使用标签插入,具有较高的功能一致性。如图1所示。
    在这里插入图片描述

    图1 总体设计原则

    3.1.1 功能模块图

    在这里插入图片描述
    图2 功能模块图

    3.1.2 类图

    在这里插入图片描述
    图3 类图

    3.1.3 用例图


    图4 用例图

    3.2 可行性分析

    可行性分析包括技术性、经济可行性、社会可行性及其它考虑。
    为避免浪费投资,提高软件生产的成功率。这是解决问题的实际目的,问题可以短时间以小成本解决[16]。
    下面对基于基于物品的协同过滤算法实现图书推荐系统开发进行判断和考察,主要从技术、经济、社会几个方面来分析。
    技术可行性:
    主要分析技术要求的技术可能性可以完成开发任务,硬件和软件可以满足开发人员的需求。功能强大的JetBrains是该软件中使用的开发工具[14]。
    PyCharm 2018.2.3 x64,强大的扩展能力是该软件的特性,该软件对于系统编写及完善有良好的支持效果,也是众多Pythoner喜爱的编译器。随着互联网行业的迅猛发展,软件开发平台及硬件技术同时不断更新进步。大容量、可靠性的提高、低价格也使得软件开发是可行的。本系统的编译需求完全可以由Pycharm编译器承担。
    综合以上情况及考虑,本系统的开发在技术上是完全可行的。
    经济可行性:
    由于本系统较为小型轻便,开发成本较低。此外,该软件稳定、后期维护简单、实用,一旦开发完成即可长期使用。当用户有了新的需求时,只需要根据需求,在原有代码基础上进行更改,维护成本较低。
    综合以上情况及靠背,本系统在经济上是完全可接受的。
    社会可行性:
    法律因素:本系统是本人处于兴趣爱好,独立完成开发的。基于Python完成,同时借鉴市场上同类软件的功能,收集并归纳用户需求,制订设计思路,结合实际中存在的实体,进行创新及开发的。
    用户使用可行性:本系统对用户的要求,没有复杂繁琐的操作,简单易用。使用软件的用户, 在了解了简单的流程后就可以对后台进行管理,没有额外的学习使用环节,节约成本。
    由以上分析可知,本系统在社会可行性方面是完全可行的。

    3.3 系统数据库设计

    根据系统业务和DBMS的需求,建立了最佳的数据存储模型。另外,通过建立数据库内的表结构与表与表之间的关系的处理,能够有效地将数据存储到应用系统中,高效访问存储的数据[15]。好的数据库设计需要以下部分。
    减少数据冗余;
    避免数据维护异常;
    节约存储空间;
    高效的访问;
    需求分析。
    同时需要分析数据和属性各自的特点,以便了解系统中所要存储的数据、了解数据的存储特点、了解数据的存储周期。需求分析中需要了解的问题是实体之间的关系、包含的属性。
    该系统采用MySQL数据库,保存用户的用户名、密码等数据。
    数据库的主要表如下:
    用户(账号,密码)如表1所示。
    图书(标题、评分、简介)如表2所示。
    在这里插入图片描述

    第四章 详细设计

    4.1 页面设计

    4.1.1 登录与注册

    登录与注册设计采用BootStrap的Navbar导航条,插入Button和Entry对象来创建按钮及输入框,并设置了绑定变量来获取输入框输入[16]。
    具体实现步骤为:
    a.设置导航条,其中包括图书推荐系统、图书列表、用户评价后的图书、管理。
    b.登录部分设置两个标签,分别命名为“用户名”、“密码”,放置两个输入框,两个按钮“登录”和“注册”。注册部分设置三个标签分别为“用户名”、“密码”,“确认密码”放置三个输入框,一个“注册按钮”。
    c.密码的文本设置输入密码后显示*号。
    在这里插入图片描述
    图5 登录界面
    在这里插入图片描述
    图6 注册界面

    4.1.2 图书列表部分

    在该部分,选择Web API类型的MVT,使用Django是一款python的web开发框架:与MVC有所不同,属于MVT框架。
    m表示model,负责与数据库交互。
    v表示view,是核心,负责接收请求、获取数据、返回结果。
    t表示template,作用就是将内容反馈并呈现在浏览器上。
    通过与数据库的交互,获取数据并返回结果到图书列表的界面上。
    界面如图7所示。
    在这里插入图片描述
    图7 图书列表部分

    4.1.3 图书详情部分

    首先获取到图书的图片,通过book.title获取标题并将其显示在界面上,通过book.stars获取评分情况将其显示。通过book.desc得到图书简介。
    这部分最关键的部分是显示与此书相似的图书,对训练的结果中的相似图书按照一定的格式显示在页面上。如图8、图9所示。
    判断是否曾经对其进行评分,如果评分过,显示之前的评分。反之,提示用户“您还没有评分过,给个评分吧”。如图10、图11所示。
    在这里插入图片描述
    图8 图书详情界面
    在这里插入图片描述
    图9 图书推荐界面
    在这里插入图片描述
    图10 评分界面
    在这里插入图片描述
    图11 历史评分列表与推荐界面

    4.2 Python实现核心功能

    4.2.1 跳转方式

    当用户在aw和bw未登录时,在SSO上设置登录态,那么在aw和bw上应该设置登录态。如上所述,还是应该在aw和bw上设置各自的登录态,这样在访问aw时首先会在aw域上检测授权,如果没有授权,则跳转到SSO进行登录授权[17]。但是aw和bw应该为登录态一般设为浏览器进程存活期,即aw和bw的登录态的存活期直到浏览器关闭。SSO域上登录态的存活期取决于具体的业务,本系统中设为7天。代码如下:
    app = Flask(name)
    app.config[‘SECRET_KEY’] = os.urandom(24) # 服务器启动一次上次的session就清除,因为设置为随机产生的24位的字符,也就是说每次运行服务器都是不同的。
    app.config[‘PERMANENT_SESSION_LIFETIME’] = timedelta(days=7) # 设置session的保存时间。

    4.2.2 数据库连接

    首先创建数据库连接,打开数据库连接。代码如下:
    import pymysql
    def create_connection():
    db = pymysql.connect(“localhost”, “root”, “12345678”, “bookrecommend”)
    return db

    4.2.3 ItemCF算法的实现

    1.ItemCF算法的实现是本图书推荐系统的核心内容,首先要计算出物品之间的相似度,相似度是推荐的关键评判指标,然后根据物品的相似度,并结合用户的历史行为,即那里评分矩阵,从而给用户生成可靠的推荐列表。
    具体步骤:
    首先建立物品的同现矩阵。
    其次建立用户对物品的评分矩阵。
    最后矩阵计算推荐结果。
    def ItemSimilarity(train):
    # 物品-物品的共同矩阵
    C = dict()
    # 物品被多少个不同用户购买
    N = dict()
    for u, items in train.items():
    for i in items.keys():
    N.setdefault(i, 0)
    N[i] += 1
    C.setdefault(i, {})
    for j in items.keys():
    if i == j:
    continue
    C[i].setdefault(j, 0)
    C[i][j] += 1

    2.计算问题之间的相似度
    W = dict()
    for i, related_items in C.items():
    W.setdefault(i, {})
    for j, cij in related_items.items():
    W[i][j] = cij / math.sqrt(N[i] * N[j])
    return W
    问题i与问题j之间的相似度这么定义:同时关注问题i与问题j的人数/关注问题i人数关注问题j的人数的平方根[18]。
    由此我们就需要计算n个问题之间两两相似度,就是一个对角线为1的对称邻接矩阵,也就是说,想要成功求出n个问题之间的相似度,至少需要计算n
    (n-1)/2次,这样才可以计算n个问题之间的两两相似度。
    推荐前K个用户
    def Recommend(train, user_id, W, K):
    rank = dict()
    if user_id not in train: return []
    action_item = train[user_id]
    for item, score in action_item.items():
    for j, wj in sorted(W[item].items(), key=lambda x:x[1], reverse=True)[0:K]:
    if j in action_item.keys():
    continue
    rank.setdefault(j, 0)
    rank[j] += score * wj
    return sorted(rank.items(), key=lambda x:x[1], reverse=True)

    第五章 系统测试

    测试就是为了寻找程序中的错误,一个成功的测试就是发现至今尚未发现的错误的测试。在这样一个找错误的过程中,每发现一个错误都是值得高兴的,因为这可以又为以后排除了一个潜在的隐患,同时系统如果要是经过测试投入使用后再发现系统的错误,那么将会花费更多的物力和财力才能解决问题,这样无疑会造成投入成本增加,更严重的甚至导致该系统不能使用白白浪费了之前的投入。因此在系统正式投入使用之前,尽可能全面的去测试系统无疑是必不可少的,也是非常重要的[19]。
    在实际的生产生活中在真实的系统工作环境下通过与之前的需求分析进行作比较,检查实现的功能是否符合需求分析的要求,是否满足客户要求。系统测试主要有两种常用的方法,黑盒测试和白盒测试,它主要是根据是否关心程序的内部结构来划分的。所谓的黑盒测试是指不关心程序如何编写的,内部结构如何,只关心输入和输出的结果是否的正确。白盒测试是将程序看成一个透明的盒子,进而分析程序内部的运行情况是否正确。
    测试就是通过静态审查或者运行程序从而找出软件实现过程中是否与需求存在偏差。除此之外,测试还需要软件质量进行度量,这样可以全面掌控软件情况,从而及时采取措施推进问题修复。测试的原则之一是测试应当尽早介入,这样的话,在研发阶段,越早发现问题,造成的损失越小,所以该问题的修复成本就越低。在寻找错误的过程中,不断完善系统,尽量的避免问题出现与系统投入使用过后,修复成本大幅度增加的情况。因此,进行全面的系统测试是不可或缺的[20]。
    在实际的测试流程中,将在设计阶段根据需求撰写测试用例,并根据实现情况对用例进行补充及修改,验证需求实现情况,提出问题并推进修复,最后给出软件质量度量。
    执行程序给定输入,并校验程序输出是否符合预期结果是黑盒测试的标准流程。它不必关心程序的内部实现逻辑,不需要涉及代码层面[2]。与之对应的是白盒测试,大多是通过静态代码审查的方式,不运行程序,凭借程序实现的逻辑结构,来检查程序是否满足需求,符合预期。

    5.1 测试的定义及其重要性

    5.1.1 测试的定义

    软件测试是直接影响软件质量评价的重要部分,这是用来衡量实际开发的系统与预期结果之间的审核比对过程,查缺补漏。软件测试需要采用有效的方法,及时发现问题,防止后续出现难以掌控的状态。测试的作用就是在衡量成本的条件下,尽可能的找出问题并推动问题修复,为软件质量作出度量,避免问题出现在系统投入使用后,减少修复成本。

    5.1.2 测试的重要性

    随着软件行业的蓬勃发展,人们更加关注的是软件质量的实现,因此软件测试就愈发重要。在软件工程中,这是必不可缺的一环。在操作和维护阶段之前,应确保软件的质量,然后在操作和维护阶段向用户提供软件产品。随着系统测试越来越重要,测试理论、测试方法越来越科学,普遍认为:软件测试应该在项目之初就进行介入。虽然软件测试的原则之一就是测试不可能发现所有BUG,软件必定是存在BUG的。但是为了减少错误的引入,我们可以使用完备的开发过程、有效的开发方法和新的语言,但这些方法只是最大程度的降低了错误。通过测试来检测这些误差是很有必要的[18]。BUG具有集群性,符合“二八原则”。测试过程中,通过统计问题分布,可以推算哪些模块需要继续进行测试。

    5.2 测试方法及过程

    白盒测试是基于代码的一种测试设计方法。测试人员通过审查代码、明晰代码实现逻辑后,以验证程序逻辑为目标,构造测试用例及数据。与之对应的黑盒测试,不关心程序内部的实现逻辑,只着眼于外部结构,将程序视作一个“黑盒子”,输入测试数据,查看输出结果与预期结果是否一致[13]。运行程序、进行输入,然后得到实际结果从而验证软件是否满足需求。
    探索性测试是一种测试思维与理念,在对系统有一定了解的情况下,不严格拘泥于先设计后执行的测试思路。探索性测试是设计与执行同时进行的,实践表明,探索性测试往往能暴露出更多的问题。
    在本系统中,综合采用了两种方法,还结合了探索性测试的思路,使测试过程可以暴露更多问题。
    本次测试主要达到以下测试目的:
    (1)功能检查:根据需求与实现产品,验证功能需求实现情况,检查功能实现是否有遗漏偏差。
    (2)数据检查:主要对用户、登录注册信息进行检查,检查数据库与程序之间的交互部分。
    (3)性能检查:针对于本次的系统,该项主要是检查推荐的图书,是否存在错误现象
    (4)稳定性检查:由于事件读取是从鼠标键盘上获取的,而导致后台响应频率较高,所以要检查程序是否出现运行终止、重启等现象。

    5.3 各模块具体测试

    5.3.1登录测试

    对用户输入的用户名,密码进行验证匹配。与pymysql结合简化了SQL查询,并与pymysql.connect协同工作将使编码量大为减少。
    测试编号:1
    描述:本测试用例用于验证系统用户登录
    前提:用户已进入系统且进入用户登录页面
    备注:使用正确的用户名和密码登录系统,与数据库信息进行验证,验证成功后,必须进行登录操作的二次检查操作,即使用错误的密码登录,也需要验证系统登录验证是否准确,以及相关的错误反馈信息是否正确。
    具体步骤:
    1.进入图书推荐系统
    2.进入主页面之后,在“用户登录”部分、在对应的文本框中分别输入用户名和密码信息。
    3.单击“登录”按钮,启动系统登录确认
    4.在页面“用户登录”区域,用户名文本框和密码文本框中分别输入错误或者不存在的用户名和密码。
    输入值:用户名,密码
    期望结果:步骤2验证通过,登录成功。步骤4准确反馈错误信息(密码错误、账号不存在)。
    实际结果:步骤2验证通过,登录成功。步骤4准确反馈错误信息(密码错误、账号不存在)。
    是否通过:通过

    5.3.2 注册测试

    在注册的过程中,加入校验。经测试无误。
    function onregistclick() {
    var username = $("#name").val();
    var password = $("#password").val();
    var repassword = $("#repassword").val();
    if(!username) {
    alert(‘用户名不能为空’);
    return;
    }
    if(!password) {
    alert(‘密码不能为空’);
    return;
    }
    if(password!==repassword) {
    alert(‘两次密码不一致’);
    return;
    }
    $.post("/regist", {“username”:username, “password”:password}, function (resp) {
    if(resp&&resp.result){
    window.location = ‘/login’
    }else{
    alert(‘注册失败,请更换个用户名!’)
    }
    })
    }
    测试编号:2
    描述:本测试用例用于测试用户“注册账号”功能是否正确
    前提:用户进入系统,在“注册账号”功能页。在“注册账号”页面编辑后提交。
    备注:需要再次进入页面以确保数据已完全更新。
    步骤:
    1.进入图示推荐系统登陆页面,点击注册账号,进入注册账号页面。
    2.在页面输入账号信息(用户名、密码、确认密码)
    3.点击注册按钮,将新用户的账号数据添加到数据库中。
    输入值:用户名,密码,确认密码
    期望结果:用户添加账号成功。
    实际结果:用户添加账号成功。

    5.3.3 数据库连接测试

    在这里,数据库管理工具用的是Navicat,选择它的原因是,全面的图形化方式进行数据库的管理,所以非常易用而且可靠,Navicat与数据库进行连接的原理是通过SSH通道和HTTP通道,既可以最大程度上避免漏洞保护数据信息,又可以在使用时,对远端服务器的访问不受安全性影响[9]。可利用图形化界面,直接对数据库对象进行创建、编辑和删除等。
    在这里,数据库管理工具用的是Navicat,选择它的原因是,全面的图形化方式进行数据库的管理,所以非常易用而且可靠,Navicat与数据库进行连接的原理是通过SSH通道和HTTP通道,可以最大程度上避免漏洞保护数据信息,同时又具备安全性。可利用图形化界面,直接对数据库对象进行管理。
    测试编号:3
    描述:本测试用例用于测试数据库连接是否成功
    前提:注册后的用户进入系统,通过登录确认进入推荐系统页面。在后台检查用户信息进行校验。
    备注:需要再次进入页面以确保数据已完全更新。
    步骤:
    1.注册
    2、进入图书推荐系统后,通过验证进入普通用户权限界面,首页即是图书推荐系统首页。
    3.查询数据库文件看是否有新用户加入、或者是重新登录,查看是否可以成功登录。
    4.输入用户名和密码,点击“登录”按钮,更新数据库中的信息
    期望结果:用户成功登录。
    实际结果:用户成功登录。
    是否通过:通过
    5.3.4 图书详情显示测试
    测试编号:4
    描述:本测试用例测试图书列表查看是否实现。
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    步骤:
    1.进入图书推荐系统的功能页面。
    2.点击“图书列表”。
    期望结果:用户可以查看图书列表。
    实际结果:用户可以查看图书列表。
    是否通过:通过

    测试编号:5
    描述:本测试用例用于测试图书图片显示是否成功是否正确
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    步骤:
    1.进入图书推荐系统的功能页面。
    2.点击“图书列表”
    期望结果:用户能够查看图书具体信息(图片)。
    实际结果:用户能够查看图书具体信息(图片)。
    是否通过:通过

    测试编号:6
    描述:本测试用例用于测试查看图书详情是否能成功显示。
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    备注:需要再次进入页面以确保数据已完全更新。
    步骤:
    1.进入图书推荐系统主页面
    2.点击“图书列表”
    3.进入图书详情界面。
    期望结果:图书详情显示无误。
    实际结果:图书详情显示无误。
    是否通过:通过

    测试编号:7
    描述:本测试用例用于测试“评分”功能是否正确,并针对不同的评论状态,查看之前的评论或者提交评论。
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    步骤:
    1.进入图书推荐系统主页面
    2.点击“图书列表”
    3.进入图书详情界面。
    4.查看评分
    期望结果:用户如果评过,提示已经评分,若未评分,则可以进行评分。
    实际结果:用户如果评过,提示已经评分,若未评分,则可以进行评分。
    是否通过:通过

    测试编号:8
    描述:本测试用例用于测试用于测试 “显示标题、简介”等功能是否正确
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    步骤:
    1.进入图书推荐系统主页面
    2.点击“图书列表”
    3.进入图书详情界面。
    期望结果:可以正确显示标题、简介等信息。
    实际结果:可以正确显示标题、简介等信息。
    是否通过:通过
    5.3.5 图书推荐测试
    循环输出得到的相似图书,经测试,输出无误。
    {% for book in simbooks %}










    {{ book.desc }}



    {% endfor %}
    测试编号:9
    描述:本测试用例用于测试“推荐图书”功能是否能实现。
    前提:用户已经进入系统,通过登录验证进入推荐系统功能页面。
    步骤:
    1.进入图书推荐系统主页面
    2.点击“图书列表”
    3.进入图书详情界面。
    期望结果:用户可以查看“推荐图书”列表及详情信息。
    实际结果:用户可以查看“推荐图书”列表及详情信息。
    是否通过:通过

    5.4测试报告

    测试报告是测试阶段的最终文档输出。一个好的测试管理员应该具有良好的文档编撰能力。详细的测试报告包括足够的信息,包括产品质量和测试过程的评估,测试报告基于最终测试结果的测试和分析数据。以下是本系统设计的测试报告表。

    表3 测试报告表
    在这里插入图片描述

    5.5 软件评价

    软件评价是指软件正式运行后,根据需求对软件的功能、性能、结构、界面友好性等内容做出客观表述[20]。
    功能评价:
    本系统可流畅运行、界面风格简单友好,实现功能可以满足用户需求。
    数据评价:
    数据库设计合理完全可以满足使用需求,且程序与数据库交互良好,维护成本低。
    性能评价:
    主内容性能良好,系统对用户的可交互性良好,但程序打开与开始系统时,停顿时间较长。
    稳定性评价:
    程序运行过程中未发生程序终止、重启等异常现象。
    第六章 总结与展望
    本文首先阐述了推荐系统开发的经过,分析了推荐系统目前的研究状况。其次,阐述了推荐结果的主要推荐方法和评估指标。最后分析了主流的推荐算法以及它们各自的优缺点,尤其是基于物品的协同过滤算法。
    推荐系统的开发,一方面使用户和信息精确一致,另一方面降低信息过载时的信息获得成本。但是,通过新闻推荐等推荐系统进行的内容分发也会给用户带来不良影响。2017年9月19日,“人民日报”指定了在中国有名的内容出版平台的标题,强调了推荐系统的开发不仅需要满足用户的多样化和个性化的需要,还需要严密监视和过滤提高推荐系统的稳健性的信息。近年来,关于用户隐私、劝告引擎稳健性、信息过滤的论文被包含在今后劝告系统的重要研究方向的recsys会议中。
    目前,深层神经网络发展迅速,为推荐系统提供了新的思路,例如特征提取和排序法。现在越来越多的推荐引擎将传统的推荐算法与深层神经网络结合,以解决数据分区和推荐排名问题。深层神经网络与推荐系统的组合是今后推荐系统的研究课题。
    总而言之,推荐系统是一个巨大的信息系统。它依赖于引擎工作以及业务系统、日志系统和许多其他方面。同时,它结合了网络安全和数据挖掘等诸多研究领域。这可以为企业和用户带来值得详细研究的价值,因此,对此领域需要更深入的研究。
    而随着当代社会突飞猛进的发展,在可预见的将来,数据与信息的量级只会越来越大。彼时,对信息筛选的需求也会日益增长,推荐系统将会在未来造成越来越大的影响,对推荐系统的研究也将达到新的高度。
    本次课题研究是基于兴趣爱好的一次实践,兴趣爱好给予了我很大的动力,这也使我在遇到各种问题时,总能通过多种渠道找到解决方案。在一边进行学习,一边进行系统开发和设计过程中,遇到了很多靠自己无法解决的问题,走了很多弯路,但是,这次课题研究锻炼了我自我解决问题的能力,不仅让我对Python有了进一步的了解,而且还大大提高了编程能力,锻炼了自己的逻辑思维和整体设计的能力,收获颇多。通过这次课题研究设计,我意识到完整的软件开发思路是非常重要的,它关系到我们开发过程能否顺利实现。在项目开始之前,必须有软件工程的系统化的知识体系,不仅如此,还应包括软件结构位置、代码编写和模块划分和整体布局,只有做完这些步骤,才能逐步开发出想要实现的功能。这样定能达到事半功倍的效果。

    结束语

    本研究课题主要工作成果是设计并实现了一个基于物品的协同过滤算法实现图书推荐系统,主要任务有以下几个方面:
    文章的主体部分进行了主要模块的详细分析设计与实现。
    然后对主要功能点做出测试及文字说明,使内容详实。
    虽然系统已经做出来并且已经通过测试 ,但是系统的开发以及课题的书写由于能力和时间的原因,还存在着一些不足和不完善的地方。
    1.在页面展示方面,多数依赖列表形式,页面比较单调,有待改进。
    2.程序的性能需要不断优化,如何让图书更精准地推荐、后台可以更快捷的完成自己的工作,有待加强。
    3.还需要实现一些人性化的功能,如图书检索、对高质量期刊的推送等。
    在功能分门别类的编写时,遇到的问题较为基础 ,通过简单的设置断点等方式,可以成功解决,但是在将各个模块的功能进行整体的测试时 ,遇到了很多问题,甚至在开发周期中,因为一个抛出的异常,导致一周进度都没有更新 。查阅多种文档都未曾发现解决方案,最后,终于在外网某网站的一条帖子下的评论下,找到了解决问题的思路。
    之前对数据库的使用较为生疏,比如说连接数据库 、建立新表、增删改查对应的sql语句等,在课题研究的过程中,经过练习,对这类操作越发熟练 ,前端的设计在去网上参考了许多优秀的前端界面后设计的。
    通过今后对一些优秀的前端框架以及Python不断地学习与探索,在实践不断研究与摸索,做出更美观的前端界面,以及对接口进行研究,希望以后可以继续完善这个系统。
    经过一个学期的学习与工作,我终于完成了《基于物品的协同过滤算法实现图书推荐系统》这一课题。从接到题目到查找资料,学习知识,再到构思系统,动手实践,编写代码,我在了解了程序编写不易的同时,也增长了知识,积累了宝贵的实战经验。一开始对机器学习、推荐系统、推荐算法的几乎零基础,这使我在学习中遇到了一些困难,在指导老师的指导下,结合查阅相关资料,慢慢克服了困难,最终完成了这个项目。在实践的过程中,我也认识到推荐系统在我们的生活中扮演了重要的角色,为广大用户提供了便利;一个高效的推荐系统可以较为准确地预测用户的喜好偏向,从而为用户提供便捷地购买或使用体验。
    我知道我的课题研究还很不成熟,其中有许多的不足之处,我会在今后的学习与工作中深入学习推荐系统与推荐算法相关知识,努力完善自身的知识系统和编程能力,丰富自身的知识储备。本次宝贵的实践经验也让我受益匪浅,此次过程让我积累了程序开发与调试的经验,对以后从事相关工作有很大的帮助。
    参考文献
    [1]章宗杰,陈玮.基于标签扩展的协同过滤算法在音乐推荐中的应用.软件导刊,2018,17(1):99-101.
    [2]张智强,伍传敏.基于协同过滤算法的电子商务推荐系统.佳木斯大学学报:自然科学版,2018,36(4):603-606.
    [3]杨丽丽,袁浩浩.基于组合优化理论的协同过滤推荐算法.现代电子技术,2018,41(1):139-142.
    [4]肖文强,姚世军,吴善明.一种改进的top-N协同过滤推荐算法.计算机应用研究,2018,35(1):105-108.
    [5]张双庆.一种基于用户的协同过滤推荐算法.电脑知识与技术:学术版,2019(1):19-21.
    [6]苏庆,章静芳,林正鑫,李小妹,蔡昭权,曾永安.改进模糊划分聚类的协同过滤推荐算法.计算机工程与应 用,2019,55(5):118-123.
    [7]王宁,何震,黄泽,周毅鹏,武鑫良.改进协同过滤算法在服装个性化推荐的研究.湖南工程学院学报:自然科学版,2019(1):33-36.
    [8]陆俊尧,李玲娟.基于Spark的协同过滤算法并行化研究.计算机技术与发展,2019,29(1):85-89.
    [9]吴宾,娄铮铮,叶阳东.一种面向多源异构数据的协同过滤推荐算法.计算机研究与发展,2019,56(5):1034-1047.
    [10]沈鹏,李涛.混合协同过滤算法在推荐系统中的应用.计算机技术与发展,2019,29(3):69-71.
    [11]张玉叶,宿超.基于Python的协同过滤算法的设计与实现.山东广播电视大学学报,2019(2):82-85.
    [12]王嘉菲,朱志锋.基于协同过滤算法的视频智能推荐系统.湖北大学学报:自然科学版,2019,41(2):202-207.
    [13]邓亚文,罗可.一种基于用户和物品相似度的融合协同过滤推荐算法.电脑与信息技术,2019,27(1):6-10.
    [14]李淑敏,夏茂辉,赵志伟.基于spark的协同过滤推荐算法的改进.软件,2019,40(2):173-178.
    [15]张志鹏,张尧,任永功.基于时间相关度和覆盖权重的协同过滤推荐算法.模式识别与人工智 能,2019,32(4):289-297.
    [16]石京京,肖迎元,郑文广.改进的基于物品的协同过滤推荐算法.天津理工大学学报,2019(1):32-36.
    [17]谭立云,刘琳,苏鹏.图书借阅推荐系统算法的python实现.科学技术创新,2018(22):84-85.
    [18]邓园园,吴美香,潘家辉.基于物品的改进协同过滤算法及应用.计算机系统应用,2019(1):182-187.
    [19]周强,李曦.基于推荐技术的中国音乐数据库系统的设计.计算机技术与发展,2015,25(7):162-165.
    [20]曹景振,贾新磊,李松丹.基于物品的协同过滤算法在ACM在线评测推荐系统中的改进及应用.无线互联科技,2018,15(5):135-13

    源码下载链接

    原文下载链接

    原文

    源码下载链接

    https://download.csdn.net/download/ancientear/15036935
    源码下载链接

    展开全文
  • KNN算法原理及实现

    千次阅读 2018-09-12 14:30:30
     kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来...

    KNN算法原理及实现

    1、KNN算法概述

        kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 

    2、KNN算法介绍

       最简单最初级的分类器是将全部的训练数据所对应的类别都记录下来,当测试对象的属性和某个训练对象的属性完全匹配时,便可以对其进行分类。但是怎么可能所有测试对象都会找到与之完全匹配的训练对象呢,其次就是存在一个测试对象同时与多个训练对象匹配,导致一个训练对象被分到了多个类的问题,基于这些问题呢,就产生了KNN。

         KNN是通过测量不同特征值之间的距离进行分类。它的的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。K通常是不大于20的整数。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。

         下面通过一个简单的例子说明一下:如下图,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。

    由此也说明了KNN算法的结果很大程度取决于K的选择。

         在KNN中,通过计算对象间距离来作为各个对象之间的非相似性指标,避免了对象之间的匹配问题,在这里距离一般使用欧氏距离或曼哈顿距离:

         同时,KNN通过依据k个对象中占优的类别进行决策,而不是单一的对象类别决策。这两点就是KNN算法的优势。

        K 近邻算法使用的模型实际上对应于对特征空间的划分。K 值的选择,距离度量和分类决策规则是该算法的三个基本要素:

    (1)K 值的选择会对算法的结果产生重大影响。K值较小意味着只有与输入实例较近的训练实例才会对预测结果起作用,但容易发生过拟合;如果 K 值较大,优点是可以减少学习的估计误差,但缺点是学习的近似误差增大,这时与输入实例较远的训练实例也会对预测起作用,使预测发生错误。在实际应用中,K 值一般选择一个较小的数值,通常采用交叉验证的方法来选择最优的 K 值。随着训练实例数目趋向于无穷和 K=1 时,误差率不会超过贝叶斯误差率的2倍,如果K也趋向于无穷,则误差率趋向于贝叶斯误差率;

    (2)该算法中的分类决策规则往往是多数表决,即由输入实例的 K 个最临近的训练实例中的多数类决定输入实例的类别;

    (3)距离度量一般采用 Lp 距离,当p=2时,即为欧氏距离,在度量之前,应该将每个属性的值规范化,这样有助于防止具有较大初始值域的属性比具有较小初始值域的属性的权重过大。

    3、KNN算法描述

         接下来对KNN算法的思想总结一下:就是在训练集中数据和标签已知的情况下,输入测试数据,将测试数据的特征与训练集中对应的特征进行相互比较,找到训练集中与之最为相似的前K个数据,则该测试数据对应的类别就是K个数据中出现次数最多的那个分类。KNN算法描述如下:

    1)、初始化训练集和类别;

    2)、计算测试集样本与训练集样本的欧氏距离;

    3)、根据欧氏距离大小对训练集样本进行升序排序;

    4)、选取欧式距离最小的前K个训练样本,统计其在各类别中的频率;

    5)、返回频率最大的类别,即测试集样本属于该类别。

    4、Matlab代码实现

    在理解了算法步骤之后,就能很容易地实现算法,Matlab作为一种研究数据的优秀工具,是实现和理解这个算法不错的选择。

    %实现KNN算法
    %%算法描述
    %1、初始化训练集和类别;
    %2、计算测试集样本与训练集样本的欧氏距离;
    %3、根据欧氏距离大小对训练集样本进行升序排序;
    %4、选取欧式距离最小的前K个训练样本,统计其在各类别中的频率;
    %5、返回频率最大的类别,即测试集样本属于该类别。
    close all;
    clc;
    %%算法实现
    %step1、初始化训练集、测试集、K值
    %创建一个三维矩阵,二维表示同一类下的二维坐标点,第三维表示类别
    trainData1=[0 0;0.1 0.3;0.2 0.1;0.2 0.2];%第一类训练数据
    trainData2=[1 0;1.1 0.3;1.2 0.1;1.2 0.2];%第二类训练数据
    trainData3=[0 1;0.1 1.3;0.2 1.1;0.2 1.2];%第三类训练数据
    trainData(:,:,1)=trainData1;%设置第一类测试数据
    trainData(:,:,2)=trainData2;%设置第二类测试数据
    trainData(:,:,3)=trainData3;%设置第三类测试数据
    trainDim=size(trainData);%获取训练集的维数
    testData=[1.6 0.3];%设置1个测试点
    K=7;
    %%分别计算测试集中各个点与每个训练集中的点的欧氏距离
    %把测试点扩展成矩阵
    testData_rep=repmat(testData,4,1);
    %设置三个二维矩阵存放测试集与测试点的扩展矩阵的差值平方
    %diff1=zero(trainDim(1),trianDim(2));
    %diff2=zero(trainDim(1),trianDim(2));
    %diff3=zero(trainDim(1),trianDim(2));
    for i=1:trainDim(3)
        diff1=(trainData(:,:,1)-testData_rep).^2;
        diff2=(trainData(:,:,2)-testData_rep).^2;
        diff3=(trainData(:,:,3)-testData_rep).^2;
    end
    %设置三个一维数组存放欧式距离
    distance1=(diff1(:,1)+diff1(:,2)).^0.5;
    distance2=(diff2(:,1)+diff2(:,2)).^0.5;
    distance3=(diff3(:,1)+diff3(:,2)).^0.5;
    %将三个一维数组合成一个二维矩阵
    temp=[distance1 distance2 distance3];
    %将这个二维矩阵转换为一维数组
    distance=reshape(temp,1,3*4);
    %对距离进行排序
    distance_sort=sort(distance);
    %用一个循环寻找最小的K个距离里面那个类里出现的频率最高,并返回该类
    num1=0;%第一类出现的次数
    num2=0;%第二类出现的次数
    num3=0;%第三类出现的次数
    sum=0;%sum1,sum2,sum3的和
    for i=1:K
        for j=1:4
            if distance1(j)==distance_sort(i)
                num1=num1+1;
            end
            if distance2(j)==distance_sort(i)
                num2=num2+1;
            end
            if distance3(j)==distance_sort(i)
                num3=num3+1;
            end
        end
        sum=num1+num2+num3;
        if sum>=K
            break;
        end
    end
    class=[num1 num2 num3];
    
    classname=find(class(1,:)==max(class));
    fprintf('测试点(%f %f)属于第%d类',testData(1),testData(2),classname);
    %%使用绘图将训练集点和测试集点绘画出来
    figure(1);
    hold on;
    for i=1:4
        plot(trainData1(i,1),trainData1(i,2),'*');
        plot(trainData2(i,1),trainData2(i,2),'o');
        plot(trainData3(i,1),trainData3(i,2),'>');
    end
    plot(testData(1),testData(2),'x');
    text(0.1,0.1,'第一类');
    text(1.1,0.1,'第二类');
    text(0.1,1,'第三类');

    5、测试结果

    对于测试点testData=[1.6 0.3],测试结果如下:

    图中,星形表示第一类训练样本点,圈儿代表第二类训练样本点,三角形代表第三类训练样本点,x代表测试样本点;

    测试点(1.600000 0.300000)属于第2类>>

     

    展开全文
  • PS中磁性套索工具实现算法

    千次阅读 2014-11-28 15:52:07
    Photoshop中磁力套索的一种简陋实现(基于Python)   经常用Photoshop的人应该熟悉磁力套索(Magnetic Lasso)这个功能,就是人为引导下的...在研发领域一般这么叫,通常管这种边缘提取的办法叫Intelligent Scissors或
  • (1)下载遗传算法需要的工具箱,Genegic Algorithm Toolbox(GA工具箱)。下载方法:在百度中输入关键字“Genegic Algorithm 官网”,然后点击第二个(Genegic Algorithm Toolbox)网页进入后,点击Download即下载...
  • Java非对称加密源码实例 1个目标文件 摘要:Java源码,算法相关,非对称加密 Java非对称加密源程序代码实例,本例中使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。 设定字符串为“张三,你好,我是李四”...
  • 下面就举一个遗传算法的具体实例,求Rastrigin函数的最小值。 1.Rastrigin’s 函数  在遗传算法中,经常使用一个函数来测试遗传算法,这个函数就是Rastrigin函数,对于有两个独立变量的 Rastrigin函数,其定义的...
  • 什么是哈希算法

    万次阅读 多人点赞 2018-12-03 10:39:31
    哈希算法的基本含义    哈希是密码学的基础,理解哈希是理解数字签名和加密通信等技术的必要前提。    哈希,英文是 hash ,本来意思是”切碎并搅拌“,有一种食物就叫 Hash ,就是把食材切碎并搅拌一下做成...
  • 它与传统算法不同,依赖梯度信息,而是通过模拟自然进化过程来搜索最优解。 例子:兔子的遗传进化 有人说,现代医学阻碍了人类的进化?你怎么看? 2.发展历程 遗传算法由密歇根大学的约翰·霍兰德和他的...
  • 决策树算法(有监督学习算法

    千次阅读 2017-11-24 11:26:19
       决策树(Decision Tree)算法是根据数据的属性采用树状结构建立决策模型,这个模型可以高效的对未知的数据进行分类。决策树模型常常用来解决分类和回归问题。如今决策树是一种简单但是广泛使用的分类器。常见的...
  • 数据平衡问题——SMOTE算法赏析

    千次阅读 2019-02-23 13:52:13
    春节前后好久没有总结问题了,... 打标问题主要是业务熟悉,主观上分类很容易分错,在分类的时候一定要让运营方来确定分类标准。 2、数据平衡问题。T:F为1:10,重新筛选样本以后达到了T:F为1:17。  实在是数...
  • 数学建模算法学习——各类模型算法汇总

    千次阅读 多人点赞 2019-09-08 14:28:50
    数据分析类算法一览 100个经典动态规划方程 优化问题 线性规划 简介:线性规划的目标函数可以是求最大值,也可以是求最小值,约束条件的等号可以是小于号也可以是大于号。为了避免这种形式多样性带来的不便,...
  • 联邦学习算法综述

    千次阅读 2021-01-06 17:06:00
    点击上方蓝字关注我们联邦学习算法综述王健宗1,孔令炜1,黄章成1,陈霖捷1,刘懿1,何安珣1,肖京21.平安科技(深圳)有限公司,广东 深圳 5180632.中国平安保...
  • 遗传算法 遗传算法是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等。遗传算法通常实现方式...
  • 1. 退火算法 优点是 局部搜索能力强,运行时间较短; 缺点 是全局搜索能力差,容易受参数的影响. 2. 遗传算法 遗传算法基本原理和方法 选择算子的c语言实现 遗传算法-课本 2.1 本质 是适应算法,应用最多的是系统最...
  • 此文章只是学习笔记,具有任何参考价值。 学习网站:http://c.biancheng.net/view/1902.html 回归是数学建模、分类和预测中最古老但功能非常强大的工具之一。 回归通常是机器学习中使用的第一个算法。通过学习...
  • 推荐算法概述

    千次阅读 2019-06-26 17:17:23
    1 协同过滤推荐算法总结 推荐算法具有非常多的应用场景和商业价值,因此对推荐算法值得好好研究。 推荐算法种类很多,但是目前应用最广泛的应该是协同过滤类别的推荐算法,本文就对协同过滤类别的推荐算法做一个...
  • 带你入门遗传算法,解读遗传算法的实现过程,通过求解TSP和mTSP问题加深对遗传算法的理解,你会发现遗传其实很有趣~
  • FPGA算法设计随笔

    千次阅读 2018-06-09 10:30:16
    因此,进行算法设计时,算法设计中需要表示的数字用到的小数、符号、无穷大、整数、浮点数等等对应硬件来说都是一串0和1组合的数字。因此,当FPGA工程师设计算法时,需要对这些数字转换。一般转换为定点数值表示方式...
  • 遗传算法简介

    千次阅读 2018-07-10 15:10:57
    阅读材料1——遗传算法遗传算法(Genetic Algorithm,简写为遗传算法)试图从解释自然系统中生物的复杂适应过程入手,采用生物进化的机制来构造人工系统模型。它是从达尔文进化论中得到灵感和启迪,借鉴自然选择和...
  • 简单来说,这三要素之间的关系,可以用下面这幅图来表示: 总结成一句话:算法通过在数据上进行运算产生模型。 3.1 数据 输入给计算机的图片或者视频是原始数据,由于计算机只能处理数值,而不是图片或者文字。所以...
  • 如何进行自动驾驶算法开发

    千次阅读 2019-10-10 17:36:01
    文章目录自动驾驶算法开发思路驾驶场景大数据采集与应用 就目前的自动驾驶发展情况来看,功能产品解决方案应该从特定区域的低速自动驾驶产品做起,而后逐步发展到高速公路、环路等结构化道路并至直复杂城市工况下...
  • 数据平衡imblearn算法汇总

    万次阅读 多人点赞 2018-04-16 19:18:11
    数据平衡,imblearn,算法汇总
  • 深度学习十大算法

    千次阅读 2019-02-13 16:40:22
    首先先让我们来定义一下什么是“深度学习”。对很多人来说,给“深度学习”下一个定义确实很有挑战,因为在...所以当你没有足够的数据或者训练的资源时,迁移学习是非常有用的一个工具
  • 路径规划基本介绍(一)

    万次阅读 多人点赞 2019-04-24 21:30:03
    一、路径规划的作用 路径规划主要是让目标对象在规定范围内的区域内找到一条从起点到终点的无碰撞安全路径。路径规划中有静态路径...障碍物的环境表示 c.规划方法 d.搜索方法 二、路径规划需要考虑的几个方面 1.起...
  • 五大常用经典算法—分治算法

    千次阅读 多人点赞 2021-03-12 10:23:43
    分治算法(divide and conquer)是五大常用算法(分治算法、动态规划算法、贪心算法、回溯法、分治界限法)之一,很多人在平时学习中可能只是知道分治算法,但是可能并没有系统的学习分治算法,本篇就带你较为全面的去...
  • 基础算法图解

    千次阅读 2018-07-02 20:16:33
    一、算法简介 二、选择排序 三、递归 四、快速排序 五、散列表 六、广度优先搜索 七、狄克斯特拉算法 八、贪婪算法 九、近似算法 十、动态规划 十一、K最近邻算法 十二、其他...
  • 我应该使用哪种机器学习算法

    千次阅读 2018-12-16 14:56:05
    我应该使用哪种机器学习算法? 该资源主要面向初学者到中级数据科学家或分析师,他们有兴趣识别和应用机器学习算法来解决他们感兴趣的问题。 当面对各种各样的机器学习算法时,初学者提出的一个典型问题是“我应该...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 75,624
精华内容 30,249
关键字:

下面不属于算法表示工具的是