Python程序设计开发宝典
上QQ阅读APP看书,第一时间看更新

4.4 精彩案例赏析

示例4-1 输入若干个成绩,求所有成绩的平均分。每输入一个成绩后询问是否继续输入下一个成绩,回答yes就继续输入下一个成绩,回答no就停止输入成绩。

        numbers=[]                                    #使用列表存放临时数据
        while True:
            x=input(’请输入一个成绩:')
            try:                                      #异常处理结构有关知识见第11章
                numbers.append(float(x))
            except:
                print(’不是合法成绩’)
            while True:
                flag=input(’继续输入吗?(yes/no)')
                if flag.lower() not in ('yes', 'no'): #限定用户输入内容必须为yes或no
                    print(’只能输入yes或no')
                else:
                    break
            if flag.lower()=='no':
                break

        print(sum(numbers)/len(numbers))

示例4-2 编写程序,判断今天是今年的第几天。

        import time

        date=time.localtime()                               #获取当前日期时间
        year, month, day=date[:3]
        day_month=[31,28,31,30,31,30,31,31,30,31,30,31]
        if year% 400==0 or (year% 4==0 and year% 100! =0):  #判断是否为闰年
            day_month[1]=29
        if month==1:
            print(day)
        else:
            print(sum(day_month[:month-1])+day)

Python标准库datetime提供了datetime和timedelta对象可以很方便地计算指定年、月、日、时、分、秒之前或之后的日期时间,还提供了返回结果中包含“今天是今年第几天”“今天是本周第几天”等答案的timetuple()函数,等等。

    >>>import datetime
    >>>Today=datetime.date.today()
    >>>Today
    datetime.date(2016,10,8)
    >>>Today-datetime.date(Today.year,1,1)+datetime.timedelta(days=1)
    datetime.timedelta(282)
    >>>Today.timetuple().tm_yday                #今天是今年的第几天
    282
    >>>Today.replace(year=2013)                 #替换日期中的年
    datetime.date(2013,10,8)
    >>>Today.replace(month=1)                   #替换日期中的月
    datetime.date(2016,1,8)
    >>>now=datetime.datetime.now()
    >>>now
    datetime.datetime(2016,10,8,15,55,16,272174)
    >>>now.replace(second=30)                   #替换日期时间中的秒
    datetime.datetime(2016,10,8,15,55,30,272174)
    >>>now+datetime.timedelta(days=5)           #计算5天后的日期时间
    datetime.datetime(2016,10,13,15,55,16,272174)
    >>>now+datetime.timedelta(weeks=-5)         #计算5周前的日期时间
    datetime.datetime(2016,9,3,15,55,16,272174)
    >>>def daysBetween(year1, month1, day1, year2, month2, day2):
        from datetime import date               #计算两个日期之间相差多少天
        dif=date(year1, month1, day1)-date(year2, month2, day2)
        return dif.days

    >>>daysBetween(2016,12,11,2016,11,27)
    14
    >>>daysBetween(2016,12,11,2011,11,27)
    1841

另外,标准库calendar也提供了一些与日期操作有关的方法。例如:

    >>>import calendar                          #导入模块
    >>>print(calendar.calendar(2016))           #查看2016年的日历表,结果略
    >>>print(calendar.month(2016,11))           #查看2016年11月份的日历表
    >>>calendar.isleap(2016)                    #判断是否为闰年
    True
    >>>calendar.weekday(2016,10,26)             #查看指定日期是周几
    2

当然,也可以自己编写代码模拟Python标准库calendar中查看日历的方法。

    from datetime import date

    daysOfMonth=[31,28,31,30,31,30,31,31,30,31,30,31]

    def myCalendar(year, month):
        #获取year年month月1日是周几
        start=date(year, month,1).timetuple().tm_wday
        #打印头部信息
        print('{0}年{1}月日历’.format(year, month).center(56))
        print(’日\t一\t二\t三\t四\t五\t六’)
        #获取该月有多少天,如果是2月并且是闰年,适当调整一下
        day=daysOfMonth[month-1]
        if month==2:
            if year% 400==0 or (year% 4==0 and year% 100! =0):
                day+=1
        #生成数据,根据需要在前面填充空白
        result=[''*8 for i in range(start+1)]
        result+=list(map(lambda d: str(d).ljust(8), range(1, day+1)))
        #打印数据
        for i, day in enumerate(result):
          if i! =0 and i% 7==0:
              print()
          print(day, end='')
      print()

  def main(year, month=-1):
      if type(year)! =int or year<1000 or year>10000:
          print('Year error')
          return
      if type(month)==int:
          #如果没有指定月份,就打印全年的日历
          if month==-1:
              for m in range(1,13):
                  myCalendar(year, m)
          #如果指定了月份,就只打印这一个月的日历
          elif month in range(1,13):
              myCalendar(year, month)
          else:
              print('Month error')
              return
      else:
          print('Month error')
          return

  main(2017)

示例4-3 编写代码,输出由星号*组成的菱形图案,并且可以灵活控制图案的大小。

  def main(n):
      for i in range(n):
          print(('* '*i).center(n*3))
      for i in range(n,0, -1):
          print(('* '*i).center(n*3))

图4-5和图4-6分别为参数n=6和n=10的运行效果。

图4-5 n=6的运行效果

图4-6 n=10的运行效果

示例4-4 快速判断一个数是否为素数。

        n=input("Input an integer:")
        n=int(n)
        if n==2:
            print('Yes')
        #偶数必然不是素数
        elif n% 2==0:
            print('No')
        else:
            #大于5的素数必然出现在6的倍数两侧
            #因为6x+2、6x+3、6x+4肯定不是素数,假设x为大于1的自然数
            m=n %6
            if m! =1 and m! =5:
                print('No')
            else:
                for i in range(3, int(n**0.5)+1,2):
                    if n% i==0:
                        print('No')
                        break
                else:
                    print('Yes')

示例4-5 编写程序,计算组合数C(n, i),即从n个元素中任选i个,有多少种选法。

根据组合数定义,可以编写代码如下:

        import math

        def Cni1(n, i):
            return int(math.factorial(n)/math.factorial(i)/math.factorial(n-i))

虽然在Python中不用担心数字太大而超过变量的表示范围,但是计算大整数的阶乘也确实需要一些时间,尤其是上面的函数中存在大量的重复计算,严重影响速度。如果把组合数的定义展开并化简一下的话可以发现其中隐藏的规律,以Cni(8,3)为例,Cni(8,3)=8! /3! /(8-3)! =(8×7×6×5×4×3×2×1)/(3×2×1)/(5×4×3×2× 1),对于(5,8]区间的数,分子上出现一次而分母上没出现;(3,5]区间的数在分子、分母上各出现一次;[1,3]区间的数分子上出现一次而分母上出现两次。根据这一规律,可以编写如下非常高效的组合数计算程序。

        def Cni2(n, i):
            if not (isinstance(n, int) and isinstance(i, int) and n>=i):
                print('n and i must be integers and n must be larger than or equal to i.')
                return
            result=1
            Min, Max=sorted((i, n-i))
            for i in range(n,0, -1):
                if i>Max:
                    result*=i
                elif i<=Min:
                    result/=i
            return result

        print(Cni2(6,2))

Python标准库itertools提供了组合函数combinations()、排列函数permutations()、用于循环遍历可迭代对象元素的函数cycle()、根据一个序列的值对另一个序列进行过滤的函数compress()、根据函数返回值对序列进行分组的函数groupby()、返回包含无限连续值的count对象的count函数()、计算笛卡儿积的函数product()等。下面的代码演示了部分函数的用法。

        >>>import itertools
        >>>for it in itertools.combinations(range(1,5),3): #从4个元素中选3个元素的组合
            print(it, end='')
        (1,2,3) (1,2,4) (1,3,4) (2,3,4)
        >>>list(itertools.permutations([1,2,3,4],3))      #从4个元素中任选3个元素的排列
        >>>x=itertools.permutations([1,2,3,4],4)           #4个元素的全排列
        >>>for i in range(5):                              #输出前5个排列
            print(next(x), end='')
        (1,2,3,4) (1,2,4,3) (1,3,2,4) (1,3,4,2) (1,4,2,3)
        >>>x='Private Key'
        >>>y=itertools.cycle(x)                            #循环遍历序列中的元素
        >>>for i in range(20):
            print(next(y), end=', ')
        P, r, i, v, a, t, e, , K, e, y, P, r, i, v, a, t, e, , K,
        >>>for i in range(5):
            print(next(y), end=', ')
        e, y, P, r, i,
        >>>x=range(1,20)
        >>>y=(1,0)*9+(1, )
        >>>y
        (1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1)
        >>>list(itertools.compress(x, y))           #根据一个序列的值,对另一个序列进行过滤
        [1,3,5,7,9,11,13,15,17,19]
        >>>def group(v):
            if v>10:
                return'greater than 10'
            elif v<5:
                return'less than 5'
            else:
          return'between 5 and 10'
        >>>x=range(20)
        >>>y=itertools.groupby(x, group)                      #对序列元素进行分组
        >>>for k, v in y:
            print(k, ':', list(v))
        less than 5 : [0,1,2,3,4]
        between 5 and 10 : [5,6,7,8,9,10]
        greater than 10 : [11,12,13,14,15,16,17,18,19]
        >>>x=itertools.count(5,3)                             #起始值为5、步长为3的count对象
        >>>for i in range(10):
            print(next(x), end='')
        5 8 11 14 17 20 23 26 29 32
        >>>list(zip('abcde', itertools.count()))              #count对象中的元素个数是无穷的
        [('a',0), ('b',1), ('c',2), ('d',3), ('e',4)]
        >>>list(zip('abc', itertools.count()))
        [('a',0), ('b',1), ('c',2)]
        >>>list(zip('abcdefghi', itertools.count()))
        [('a',0), ('b',1), ('c',2), ('d',3), ('e',4), ('f',5), ('g',6), ('h',7), ('i',8)]
        >>>for item in itertools.product('abc', range(4)):    #笛卡儿积
            print(item, end='')
        ('a',0) ('a',1) ('a',2) ('a',3) ('b',0) ('b',1) ('b',2) ('b',3) ('c',0) ('c',1)
        ('c',2) ('c',3)
        >>>for item in itertools.product('abc', '123', 'BC'):
            print(item, end='')
        ('a', '1', 'B') ('a', '1', 'C') ('a', '2', 'B') ('a', '2', 'C') ('a', '3', 'B') ('a', '3',
        'C')('b', '1', 'B')('b', '1', 'C') ('b', '2', 'B') ('b', '2', 'C') ('b', '3', 'B') ('b',
        '3', 'C') ('c', '1', 'B') ('c', '1', 'C') ('c', '2', 'B') ('c', '2', 'C') ('c', '3', 'B')
        ('c', '3', 'C')
        >>>func=lambda x:x.isnumeric()
        >>>list(itertools.takewhile(func, '1234abcd'))        #过滤元素
        ['1', '2', '3', '4']
        >>>list(itertools.dropwhile(func, '1234abcd'))
        ['a', 'b', 'c', 'd']

示例4-6 编写代码,模拟决赛现场最终成绩的计算过程。

  while True:
      try:
          n=int(input(’请输入评委人数:'))
          if n<=2:
              print(’评委人数太少,必须多于2个人。')
          else:
              break
      except:
          pass
    scores=[]

    for i in range(n):
        #这个while循环用来保证用户必须输入0~100之间的数字
        while True:
            try:
                score=input(’请输入第{0}个评委的分数:'.format(i+1))
                #把字符串转换为实数
                score=float(score)
                assert 0<=score<=100
                scores.append(score)
                #如果数据合法,跳出while循环,继续输入下一个评委的分数
                break
            except:
                print(’分数错误’)

    #计算并删除最高分与最低分
    highest=max(scores)
    lowest=min(scores)
    scores.remove(highest)
    scores.remove(lowest)
    finalScore=round(sum(scores)/len(scores),2)

    formatter=’去掉一个最高分{0}\n去掉一个最低分{1}\n最后得分{2}'
    print(formatter.format(highest, lowest, finalScore))