COMP9021 Principles of Programming WEEK2

1. Monty Hall Problem

一个著名的博弈论游戏,起源大概是一个美国电视游戏节目叫做Let's make a deal,这个问题名字来源于节目主持人Monty Hall 。
游戏简述:
“参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车或者是奖品,选中后面有车的那扇门就可以赢得该汽车或奖品,而另外两扇门后面则各藏有一只山羊或者是后面没有任何东西。当参赛者选定了一扇门,但未去开启它的时候,知道门后情形的节目主持人会开启剩下两扇门的其中一扇,露出其中一只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。问题是:换另一扇门会否增加参赛者赢得汽车的机会率?如果严格按照上述的条件的话,答案是会。—换门的话,赢得汽车的概率是2/3。”
用python仿写这个游戏

大多程序都可以拆解为输入、处理和输出。本题就是按照这个思路写出相应的程序

1.1 输入1--模拟次数

While True:
#注意boolean的书写是大写的T,True
    try:
        n = int(input('How nany times do you want to run the simulation? '))
        #如果出现异常,不会继续运行try后面的程序行,而是跳入exceept程序段执行程序
        break
        #跳出当前循环,注意,如果有多个循环的时候,一次break不能跳出所有循环。
    except ValueError:
        print('Incorrect input, try again')
#这样实现了用户多次输入的功能,允许用户多次输入来调整想要的输入值。

这段程序使用的是之前lab1用过的try ... except ...被动异常捕获方法,但是只能保证输入值是int,而我们的输入值不仅是int,还需要>0,这里新引入一种主动异常处理方式--raise

While True:
    try:
        n = int(input('How nany times do you want to run the simulation? '))
        if n < = 0:
            raise ValueError
            #rasie命令使得程序进入except ValueError程序块,不执行后续的try程序块的命令。
        break
    except ValueError:
        print('Incorrect input, try again')

1.2 输入2--更改答案

和上文同理

while True:
    try:
        n = int(input('How nany times do you want to run the simulation? '))
        if n <= 0:
            raise ValueError
        break
    except ValueError:
        print('Incorrect input, try again')
print(n)

while True:
    switch = input('Do you want to switch? ')
    if switch in {'yes', 'Yes', 'y', 'Y'}:
        switch = True
        #由于switch只有两种情况,赋予boolean值效率最高,不要赋予其他int值。
        break
    if switch in {'no', 'No', 'n', 'N'}:
        switch = False
        break
    else:
        print('Incorrect input, try again')

1.3 游戏初始化

在三扇门后面要随机选择一个放入大奖,需要用随机函数来写,之前讲过import random,接下来的问题是查询使用哪个函数更方便。查询random的函数方法是dir(random),但是其中会有很多initilize的函数,这些函数命名两端是_,把他们删除的显示方法如下

import random
[x for x in dir(random) if not x.startswith('_')]

上文程序写法是简化的list生成方法,它等于下文程序

import random
result = []
for x in dir(random):
    if not x.startswith('_'):
        result.append(x)
result

从中选取了choice函数,因为它的属性是Choose a random element from a non-empty sequence.

from random import *
doors = ['A', 'B', 'C']
#用list记录三扇门
nb_of_wins = 0
#记录获奖次数,初始化为0
winning_door = choice(doors)
#随机从doors的list中选择一个放置大奖
first_choice = doors.pop(randrange(3))
#在index[0,2]中随机抽选一个int作为用户的初始选择index,并把doors中对应index的门assign给first_choice,再从doors中删除

1.4 游戏过程

游戏过程是主持人删除了一个错误答案,玩家决定是否更换选项。

if not switch:
#前文把switch赋予了boolean值,所以可以直接用not switch判断True or False
    second_choice = first_choice
if first_choice = winning_door:
#如果first_choice是winning_door,打开的门可以是doors剩下两扇门中的任何一个
    opened_door = doors.pop(randrange(2))
    #从剩下两扇门中随意找到一扇门打开,接下来是要看玩家选项是否switch
    if switch:
        second_choice = doors[0]
        #因为此时doors的list中只有一个元素,所以switch的话,就把doors中剩下的这个元素(index一定是0)assign给second_choice
    else:
        nb_of_wins += 1
        #因为最开始已经定义not switch时second_choice的赋值问题,所以可以直接确定中奖。
else:
    doors.remove(winning_door)
    #因为first_choice不是大奖,所以要从doors的list中删除大奖的门,大奖的门不能被主持人打开
    opened_door = doors[0]
    #此时doors的list中只剩下一个非大奖选项
    if switch:
        second_choice = winning_door
        nb_of_wins += 1      

1.5 模块组合和结果输出

输入模块、游戏初始化和游戏过程都已经写好,接下来整合模块,并且按照要求输出结果

from random import choice, randrange

while True:
    try:
        n = int(input(input('How nany times do you want to run the simulation? '))
        if n <= 0:
            raise ValueError
        break
    except ValueError:
        print('Incorrect input, try again')

while True:
    switch = input('Do you want to switch? ')
    if switch in {'y', 'Y', 'yes', "Yes'}:
        switch = True
        break
    if switch in {'n', 'N', 'no', ‘No'}:
        switch = False
        break
    print('Incorrect input, try again')

for _ in range(n):
    doors = ['A', 'B', 'C']
    winning_door = choice(doors)
    nb_of_wins = 0
    first_choice = doors.pop(randrange(3))
    if not switch:
        second_choice = first_choice

    if first_choice == winning_door:
        opened_door = doors.pop(randrange(2))
        if switch:
            second_choice = doors[0]
        else:
            nb_of_wins += 1
    else:
        doors.remove(winning_door)
        opened_door = doors[0]
        if switch:
            nb_of_wins += 1
            second_choice = winning_door
    
    print('Winning door:', winning_door)
    print('First choice:', first_choice)
    print('Opened door:', opened_door)
    print('Seconc choice:', second_choice)
    print()
print(nb_of_wins)

运行结果会发现switch的获奖概率是2/3,not switch的获奖概率是1/3。

2. PDF automated form filling

这个部分的内容没有详细讲解,主要是展示有这样的可能性。运行相关程序的时候需要install两个module,一个是PyPDF2,另一个是pyautogui,方法如下:

pip3 install PyPDF2
pip3 install pyobjc-core
pip3 install pybojc
pip3 install pyautogui

实际上打开这个程序的python源文档,并不难理解,大部分是字符串操作。

3. Elementary cellular automata (元胞自动机)

简单说:以1个符号作为种子,根据某种规则向下衍生。

3.1 规则制定

本问题的规则是根据一个输入的数转化为2进制后制定的,先要实现二进制显示8个数字的功能。以输入90为例:

bin(90)
>>> '0b1011010'

转化后数字前面有二进制符号0b,需要把它们去掉

bin(90)[2:]
>>> '1011010'

然后发现不同的数字二进制转化后并不能确定是8位数,所以要在前面补全数字0

'0' * (8 - len(bin(90)[2:]) + bin(90)[2:]
>>> '01011010'

这样的程序虽然达到了目的,但是太过复杂,想到之前提到过的格式化输出,可以利用string来帮助简化程序

f'{90:08b}'
>>> '01011010'

接下来完成输入值90的规则制定

n = f'{90:08b}'
rules = {}
for i in range(8):
    rules[(i // 4, i // 2 % 2, i % 2)] = n[7 - i]

使用python的简化写法

n = f'{90:08b}'
rules = {(i // 4, i // 2 % 2, i % 2): n[7 - i] for i in range(8)}

3.2 程序实现

def print_line(line):
#根据list调整输出格式
    for e in line:
        if e:
        #如果某个位置是非0的
            print('*', end = '')
        else:
            print(' ', end = '')
    print()
    #换行输出

n = f'{90:08b}'
rules = {(i // 4, i // 2 % 2, i % 2): int(n[7 - i]) for i in range(8)}

line = [0] * 20 + [1] + [0] * 20
#初始化,左右两边各20个0,中间有个1
print_line(line)
#输出初始化的第一行,后续行根据规则产生,边界是不能超出41个数字(20个0,1个1,20个0是初始化的位置)
for i in range(20):
#每次根据规则产生新的一行时,1的左右两侧各使用了1个0,所以只能产生20行,否则超出临界值。
    new_line = [0] * 41
    for j in range(20 - i, 20 + i + 1):
    #第i行需要根据规则产生数字的区域是[20-i, 20+i]
        new_line[j] = rules[line[j - 1], line[j], line[j + 1]]
    line = new_line
    print_line(line)
>>>
                    *                    
                   * *                   
                  *   *                  
                 * * * *                 
                *       *                
               * *     * *               
              *   *   *   *              
             * * * * * * * *             
            *               *            
           * *             * *           
          *   *           *   *          
         * * * *         * * * *         
        *       *       *       *        
       * *     * *     * *     * *       
      *   *   *   *   *   *   *   *      
     * * * * * * * * * * * * * * * *     
    *                               *    
   * *                             * *   
  *   *                           *   *  
 * * * *                         * * * * 

4. Rational Number

有理数用小数的形式表现的时候小数点后分为两个部分,有限的无规律数字组+无限的有规律的循环数字组。(Eric给的课上代码是有错误的,正确的如下)

sigma = input('Input sigma: ')
tau = input('Input tau:')
p = int(sigma) * ((10 ** (len(tau))) - 1) + int(tau)
q = ((10 ** len(tau)) -1) * 10 ** len(sigma)
print(p/q)

#[sigma * (10^{|tau|} - 1) + tau] /
#            [(10^{|tau|} - 1) * 10^{|sigma|}]
#该公式是从0.(sigma)(tau)(tau)(tau)...
          = sigma * 10^{-|sigma|} + tau(10^{-|sigma|-|tau|} +
                                        10^{-|sigma|-2|tau|} +
                                        ...)推导出来的
>>>
Input sigma: 27
Input tau:343
0.27343343343343346

在这个问题的python源文档中,有一种需要注意的函数调用方法

def f():
    return (2, 6)
def g(m, n):
    return m * n
print(g(*f()))
#注意f()前边的*,因为函数f()返回的是一个tuple(2,6),这是一个元素,而g()需要两个数的输入,所以要用*f()把tuple(2, 6)这样一个元素转化为两个元素。

再举一个例子

def f(a, b):
    return a + b
f(*(2, 6))
>>> 8

你可能感兴趣的:(COMP9021 Principles of Programming WEEK2)