Python-四则运算-蔡晓晴,杜婷萱

github链接:https://github.com/Amy-CC/Arithmetic-Operation

 

一、需求

  1.使用-n 参数控制生成题目的个数

  2.使用-r 参数控制题目中数值(自然数、真分数和真分数分母)的范围

  3.生成的题目中计算过程不能产生负数

  4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。

  5. 每道题目中出现的运算符个数不超过3个。

  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。

  7.生成的题目存入执行程序的当前目录下的Exercises.txt文件

  8.在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件

  9. 程序应能支持一万道题目的生成。

  10. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:

      Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt

    统计结果输出到文件Grade.txt,格式如下:

      Correct: 5 (1, 3, 5, 7, 9)

      Wrong: 5 (2, 4, 6, 8, 10)


二、PSP2.1表格

PSP:

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning 计划 20 30
· Estimate · 估计这个任务需要多少时间 20 30
Development 开发 1230 1665
· Analysis · 需求分析 (包括学习新技术) 120 300
· Design Spec · 生成设计文档 60 60
· Design Review · 设计复审 (和同事审核设计文档) 30 50
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 5
· Design · 具体设计 30 40
· Coding · 具体编码 900 1100
· Code Review · 代码复审 40 30
· Test · 测试(自我测试,修改代码,提交修改) 40 80
Reporting 报告 50 70
· Test Report · 测试报告 10 10
· Size Measurement · 计算工作量 10 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 40
合计   1300 1765

 

三、设计实现过程

1.功能1~9的实现步骤

  

  · 一开始题目运行采用递归的方法,后来调用的函数过多,递归函数太复杂,于是改成for生成题目

  · 查重用了字典的方法

  · 随机括号的生成费时良久,考虑到随机的各种情况,左括号和右括号的定位问题,存储结构等等问题。

  · 在寻找计算的简便方法的时候,我们找到一个函数eval(string)可以直接识别并计算一整条string。

  · 减号的判断,由于计算的方法不是根据括号从内到外计算的,要一个个计算却会使得函数过于复杂,向同学取经后,我们还是采用了负数舍去的方法。

 

2.功能10的实现过程:

   

  · 用正则表达式检查输入的文本格式是否正确

    · 根据空格split分别存储真分数等各个数值

  · 调用两个函数进行,分数与真分数互相转换

效能分析:

 CPU运行时间:

 10000道题的生成时间:

100道题的时间:

功能10:

可以看出来到了大量题目的时候,由于查重,时间复杂度成指数级上升。

代码说明

功能1~9:

    1.  #随机生成一道题目中的符号和数字

    def random_list(self):
        char_list = ["+", "-", u"×", u"÷"]
        num_list = []
        char_choice = []
        cc = ""
        loop_times = random.randint(2,4)
        for i in range(loop_times):
            static = random.randint(1, self.num_range)
            if self.num_range > 1:
                float_n = produce_random.choose_num(self)
                choice = random.choice([static, float_n])
            else:
                choice = static
            cc += str(choice)
            num_list.append(choice)
            if i < (loop_times - 1):
                cha = random.choice(char_list)
                char_choice.append(cha)
                cc += cha
        jo = str(num_list+char_choice)
        if jo in dic_t.keys():
            self.random_list()
        else:
            dic_t[jo] = 0
        return char_choice, num_list

     2.  #随机生成分数

    def choose_num(self):
        while(1):
            a = random.randint(1, self.num_range**2)
            b = random.randint(2, self.num_range)
            if Decimal(a/b) < self.num_range:
                return Fraction(a, b)

     3.  #根据符号随机加括号

    def plus_(self,char_choice, num_list):
        numble = len(num_list) + len(char_choice)
        ccc = [""] * numble
        if len(char_choice) == 1:
            pass
        else:
            """括号对数"""
            n_kuo = random.randint(0, len(char_choice) - 1)
            if n_kuo != 0:
                p_kuo_l = []
                """设置list1初值"""
                list1 = []
                for i in range(0, len(char_choice)):
                    z = 2 * i
                    """改为append"""
                    list1.append(z)
                p_kuo_l = random.sample(list1, 1)  # 按照char的数量来存,2个就是[0,2],3个[0,2,4]
                ccc.insert(p_kuo_l[0], '(')  # 插入1st左边的括号
                if len(char_choice) == 2:
                    ccc.insert(p_kuo_l[i-1] + 4, ')')
                if len(char_choice) == 3:
                    """第一个右括号"""
                    if p_kuo_l[0] == 0:
                        list1 = [4]
                    if p_kuo_l[0] == 2:
                        list1 = [p_kuo_l[0] + int(random.sample([4, 6], 1)[0])]
                    if p_kuo_l[0] == 4:
                        list1 = [8]
                    p_kuo_r = list1[:]
                    ccc.insert(p_kuo_r[0], ')')
                    x = p_kuo_l + p_kuo_r
                    if n_kuo == 2:  # 加第二个右括号
                        if x[0] == 2:
                            if x[1]==x[0]+4:
                                y = random.sample([0, x[0]], 1)
                                ccc.insert(y[0], '(')
                                if y[0] == 0:
                                    ccc.insert(x[1]+1, ')')
                                else:
                                    ccc.insert(x[0] + 8, ')')
                            else:
                                y = random.sample([x[0], x[0] + 3], 1)
                                ccc.insert(y[0], '(')
                                if y[0] == 2:
                                    ccc.insert(x[0] + 5, ')')
                                else:
                                    ccc.insert(x[0] + 7, ')')
                        if x[0] == 0:
                            y = x[0]
                            ccc.insert(y, '(')
                            ccc.insert(x[0] + 8, ')')
                        if x[0] == 4:
                            y = x[0]-2
                            ccc.insert(y, '(')
                            ccc.insert(x[0]+7, ')')
        num = 0
        cha = 0
        list_all = []  # 装数字+字符
        j = 0
        flag = True
        #标记乘号的个数
        multi_n = []
        #标记除号的个数
        divide_n = []
        for i in range(0, len(num_list)):
            if j == (len(num_list) - 1):
                j -= 1
                flag = False
            list_all.append(num_list[i])
            if i == j and flag == True:
                list_all.append(char_choice[j])
                j += 1
        j = 0
        for i in range(0, len(ccc)):
            if ccc[i] != '':
                continue
            if list_all[j]=='×':
                multi_n.append(i)
            if list_all[j]=='÷':
                divide_n.append(i)
            ccc[i] = list_all[j]
            j += 1
        return ccc, multi_n, divide_n

    4. #符号转换,把list改为str来计算,分数周围有自己的括号(计算的时候将分数看成除法来计算)

    def join_c(self, ccc, multi_n, divide_n):
        list1 = ccc[:]
        ll = []
        for i in multi_n:
            list1[i]= '*'
        for j in divide_n:
            list1[j] = '/'
        for i in list1:
            if type(i) == Fraction:
                if i.denominator == 1:
                    ll.append(str(i))
                else:
                    ll.append('('+str(i)+')')
            elif type(i) == int:
                ll.append(str(i))
            else:
                ll.append(i)
        final = ''.join(ll)
        return final

    5.   #计算

def calculate(final):
    result = eval(final)
    return result
 6. #查找小数循环,小数转分数
     def ff_c(self, demical):  # 参数为小数
        for i in range(1, 16):
            cpart = demical[:i]
            if len(cpart) < 4:
                if (cpart * i * 4) == demical[:4 * i]:  # 4次重复
                    return cpart  # 循环体
        return 0  # 找不到循环体,返回0

    #小数转分数
    def demical_to_fraction(self, digi, back_num=0):
        digi = str(digi)
        if len(digi) <= 15:  # 有限小数
            return Fraction(digi)
        real_num, dot_area = digi.split('.')
        float_num = float(digi)
        for i in range(len(digi)):
            cycle_start = dot_area[i:]
            result = self.ff_c(cycle_start)
            length = len(str(result))  # 循环体的长度
            if result:  # 存在循环体
                if i != 0:  # 不是小数点后第一位
                    new_number = float_num * (10 ** i)  # 移位数
                    self.demical_to_fraction(new_number, i)  # 递归
                    break
                else:  # 小数点后的第一位
                    fraction = Fraction(int(result), int('9' * length))  # 小数转分数
                    final_num = int(real_num) + fraction
                    return final_num / (10 ** back_num)  # 回退移的位数
        return False

  7.  #最后转为真分数输出

    def change_l(self,li):
        hh = []
        for i in li:
            if type(i) == Fraction:
                hh.append(change_ff(i))
                hh.append(' ')
                continue
            else:
                hh.append(str(i))
                hh.append(' ')
        return hh

    8. # 写入文件

    def write(self, f_line):
        self.glocount += 1
        count = int((self.glocount + 1) / 2)
        tihao = str(count) + '. '
        file = open('Exercise.txt', 'a')
        f_line = tihao + f_line + ' ='
        file.write(f_line + '\n')
        file.close()

    def write_a(self, f_line):
        self.glocount += 1
        tihao = str(int(self.glocount / 2)) + '. '
        file = open('Answers.txt', 'a')
        f_line = tihao + f_line
        file.write(f_line + '\n')
        file.close()

功能10:

      1. # 支持读取给定的文件

def read_f(name):
    with open(name) as Re:
        file = Re.readlines()
    return file

  2. #检查文件名是否符合格式

def check_file(file_name):
    if re.match(r"[a-zA-Z0-9]*\..", file_name):
        return True
    print("The file name is not right.")
    return False

  3. #转换分数AND真分数

def change_re_fra(expre):
    if re.search("[1-9]\'[1-9]/[1-9]", expre):
        l1 = expre.split("\'")
        l2 = l1[1].split("/")
        num_1 = eval(l1[0])
        num_2 = eval(l2[0])
        num_3 = eval(l2[1])
        middle_p = num_3*num_1
        num_2 += middle_p
        return str(Fraction(num_2,num_3))
    if expre == '×':
        expre = '*'
    if expre == '÷':
        expre = '/'
    return expre

def change_ff(fra):
    d = fra.denominator
    n = fra.numerator
    midde = n // d
    if midde == 0:
        return str(n)
    cha = n - d * midde
    if cha == 0:
        return str(midde)
    return str(midde)+'\''+str(Fraction(cha,d))

  4. #比对提交的和user答案一致性

def bidui(ans,user):
    corr,wrong = [],[]
    a_line = read_f(ans)
    user_line = read_f(user)
    for i in range(0,len(a_line)):
        if a_line[i] == user_line[i]:
            corr.append(str(i))
            corr.append(',')
        else:
            wrong.append(str(i))
            wrong.append(',')
    u1 = ''.join(corr)
    u2 = ''.join(wrong)
    co = 'Correct:'+str(len(corr)//2)+'('+u1+')'
    w = 'Wrong:'+str(len(wrong)//2)+'('+u2+')'
    write_Grade(co,w,'Grade.txt')
def compare(file_list):
    file = read_f(file_list[0])
    gloc = 0
    for i in file:
        i = i.rstrip()
        lii = i.split('.')
        c = lii[1].split(' ')
        strr = ''
        for j in c:
            if j != '=':
                str = change_re_fra(j)
                strr += str
        result = calculate(strr)
        f_result = demical_to_fraction(result)
        ff_result = change_ff(f_result)

        write1(ff_result, 'answer2.txt',gloc)
        gloc += 1
    bidui('answer2.txt',file_list[1])

  5.  #写入文件

def write1(result, name,glocount):
    glocount += 1
    count = glocount
    tihao = str(count) + '. '
    file = open(name, 'a')
    f_line = tihao + result
    file.write(f_line + '\n')
    file.close()

def write_Grade(c,w,name):
    file = open(name, 'a')
    file.write(c+'\n'+w)
    file.close()

 

  main函数:

if __name__ == '__main__':

    file = open('Exercise.txt', 'r+')
    file.truncate()
    file.close()
    file = open('Answers.txt', 'r+')
    file.truncate()
    file.close()
    sys.setrecursionlimit(10000)
    dic_t = {}
    if len(sys.argv)>2:
        num_range, ti_num = menu()
        if num_range > 0 and ti_num > 0:
            instance = produce_random(num_range, ti_num)
            instance.running()     
    else:
        print("输入命令不足")
def menu():
    flag = 0
    compare_arg = 0
    file_rrlist = []
    for i in range(len(sys.argv)):
        if sys.argv[i]=='-e':
            if check_file(sys.argv[i+1]):
                file_rrlist.append(sys.argv[i+1])
                compare_arg += 1
                continue
        elif sys.argv[i]=='-a':
            if check_file(sys.argv[i+1]) and compare_arg==1:
                file_rrlist.append(sys.argv[i+1])
                compare(file_rrlist)
                flag = -1
        elif sys.argv[i]=='-n' and compare_arg==0:
            ti_num = input("请输入题目数量:")
            ti_num = int(ti_num)
            if ti_num<=0:
                print("数量过少,请重试")
                break
            flag += 1
        elif sys.argv[i]=='-r' and compare_arg==0:
            num_range = input("请输入数值范围:")
            num_range = int(num_range)
            if num_range<=1:
                print("数值范围不存在")
                continue
            else:
                break
            flag +=1
    if flag == 1:
        return num_range, ti_num
    elif flag==-1:
        return -1,-1
    else:
        print("输入不正确")
        return 0, 0
    def running(self):
        #利用for循环输出多道题
        for i in range(self.num):
            while True:
                char_choice, num_list = self.random_list()
                k, multi_n, divide_n = self.plus_(char_choice, num_list)
                new_title = self.join_c(k, multi_n, divide_n)
                z = calculate(new_title)
                if z < 0:
                    continue
                final_result = demical_to_fraction(z)
                if type(final_result) == float or final_result==False:
                    continue
                if final_result.denominator > self.num_range:
                    continue
                else:
                    break
            nn_tt = self.change_l(k)
            final_result = change_ff(final_result)
            nn_tt = ''.join(nn_tt)
            print(nn_tt)
            self.write(nn_tt)
            self.write_a(str(final_result))

 测试结果分析:

  生成题目:

  10000道题,范围为1000:

   测试文件查错:

 

 

总结:

  我们采取的分工合作是蔡同学先负责写随机数、menu等等,杜同学写加括号、功能10的实现,之后由于加括号出现了很多bug,两人都在这个函数上掉了大把的头发,前期代码在后期测试时很多地方的需要上有较多的调整,对于有些功能比如查重用了比较暴力的删除法,增加了不少运行时间。

   ——蔡晓晴:我觉得这次结对编程还是获益良多的,例如我们开始没找到合适的方法来沟通、设计,到后面逐步完善我们的设计方案,修改bug。我觉得这其中彼此的思想碰撞有很大的作用,也谢谢同伴的帮助!(^-^)V

     ——杜婷萱: 一开始我们采取两个人一边写一边看的形式让我略感压力的同时,双人互挑bug的讨论比一个人的思路更广阔,纸上谈兵也能发现很多的问题,我学习到思路理清楚很重要,如果仅仅只是有个想法而不做深入思考就写代码就会写的很乱,不仅浪费时间,还会难以让同伴理解。而且双方的沟通很重要,两人最好尽量同步,现实代码总比理想的冗余了许多,结对其实更考验写代码的速度和质量。作为一个messy coder蔡同学帮助了我非常多,感谢ヽ( ̄▽ ̄)و

 

 

posted @ 2018-09-29 14:03  dtx_7777  阅读(374)  评论(0编辑  收藏  举报