zoukankan      html  css  js  c++  java
  • 结对项目——自动生成小学四则运算题目的命令行程序(基于Python)

    这个作业属于课程 软件工程
    这个作业的要求在哪里 结对项目
    这个作业的目标是 实现一个自动生成小学四则运算题目的命令行程序
    成员 3118005408 方俊涛 、3118005409 冯宇航

    GitHub

    @

    PSP

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

    效能分析

    花费时间:30min

    egg :python main.py -r 50 -n 1000

    改进前:

    通过pycharm自带的效能分析工具来看,我们的程序在creQuestion()上花费的时间是比较多的,原因是该函数在实现生成表达式时,调用了self.isRepeat(expressionList, expression) 进行查重检测,此步奏涉及到大量的遍历二叉树

        def creQuestion(self):
            """
            表达式生成主函数
            """
            expNum = self.expressionNum
            expressionList = []
            i = 0
    
            while i < expNum:
                random_num_operation = random.randint(1,self.operCount)  #运算符的数目
                is_need_parenteses = random.randint(0, 1)    #是否需要加括号
                number_of_oprand = random_num_operation + 1  # 操作数比操作符的数目多1
                exp = []
                for j in range(random_num_operation + number_of_oprand):
    
                    if j % 2 == 0:
                        # 随机生成操作数(含分数)
                        exp.append(self.getOperNum()['operStr'])
    
    
                        if j > 1 and exp[j - 1] == '÷' and exp[j] == '0':
                            while True:
                                exp[j - 1] = self.generateOperation()
                                if exp[j - 1] == '÷':
                                    continue
                                else:
                                    break
                    else:
                        # 生成运算符
                        exp.append(self.generateOperation())
    
                    if j > 3:
                        if exp[j-2] == '÷' :  #为了子表达式为真分数,÷左右又括号除外
                            if exp[j-1] > exp[j-3]:
                                t  = exp[j-1]
                                exp[j - 1] =  exp[j-3]
                                exp[j - 3] = t
    
    
                        elif exp[j-2] == '-' :
                            if exp[j-1] < exp[j-3]:
                                t  = exp[j-1]
                                exp[j - 1] =  exp[j-3]
                                exp[j - 3] = t
    
    
    
    
                # 判断是否要括号
                if is_need_parenteses and number_of_oprand != 2:
                    expression = " ".join(self.generateParentheses(exp, number_of_oprand))
                else:
                    expression = " ".join(exp)
    
                #判断是否有重复
     #           if self.expressionNum <= 500:
                if 1 :
                    if self.isRepeat(expressionList, expression) :
                       continue
                    else:
                        result = self.calculate(expression)
                        if result == "False" :
                            pass
                        else:
    
                            expressionList.append(expression)
                            #('第 %d 道题' % int(i + 1))
                            #print(expression)
                            i = i + 1
                else:
                    result = self.calculate(expression)
                    if result == "False":
                        pass
                    else:
    
                        expressionList.append(expression)
                        # ('第 %d 道题' % int(i + 1))
                        # print(expression)
                        i = i + 1
    
    
    
            return expressionList
    
    
    
    

    改进后:

    出于软件工程的实用性和需求分析,当需要生成的题目数量超过500时,由于题量较多,时间更显得重要,故判断题量较多时可不查重,改进后时间大大缩短

    通过pycharm自带的效能分析工具来看,我们改进后的程序在expression_result()上花费的时间是比较多的,原因在于为了计算答案,多次调用suffixExpression进行中缀表达式转后缀表达式并进行计算。思考无果,暂时没有好算法进行替换


    时间缩短为原来的1/37

       def expression_result(self,exp_list):
            """
            求表达式的结果
                :param exp_list: 表达式列表
                :return: None
            """
    
            self.exp_list  =exp_list
            if os.path.exists('Answer.txt'):   # 清空上一次的答案
                with open('Answer.txt', 'r+') as file:
                    file.truncate(0)
    
            for i, exp in enumerate(self.exp_list):
                order_str = str(i + 1)
                suffixExpression  = SuffixExpression(exp)
                #print("------suffixExpression:{}---------".format(str(suffixExpression.suffixToValue()) ))
                exp_value = str(suffixExpression.suffixToValue()) + '
    '
                result = "Answer"+order_str + ': ' + exp_value
    
                with open('Answer.txt', 'a+', encoding='utf-8') as f:
                    f.write(result)
    
    
    
    
    
    
    

    设计实现过程以及重要代码说明

    本次设计程序我们采取了面向对象的方法进行编程,对于整个程序进行分析,我们以生成题目、计算答案、校对答案三个功能把它分为了三个模块构造了四个类,其中BinaryTree类主要服务于Product类实现生成题目的功能

    第一个类: BinaryTree类

    该类有两个功能:生成二叉树和检查两颗二叉树是否相同

    生成二叉树: 是将传入的后缀表达式列表用二叉树进行存储

    检查两颗二叉树是否相同:实现的方法是基于递归的方法进行遍历结点

    本类是为了Product类的def isRepeat(self, express_set, expression)函数服务的,在Product类为了避免生成重复的表达式,采用了基于二叉树的形式进行查重!

    第二个类: SuffixExpression类

    该类的功能如下:

    * 将中缀表达式转化为后缀表达式(def toSuffix(self))
    * 计算后缀表达式的值( def suffixToValue(self):)
    

    其中将中缀表达式转化为后缀表达式的实现:

    
        def toSuffix(self):
            """
            中缀表达式转为后缀表达式
            :self.exp : 中缀表达式列表
            :return: result列表
            """
            if not self.exp: 
                return []
            ops_rule = {
                '+': 1,
                '-': 1,
                '×': 2,
                '÷': 2,
            }
            suffix_stack = []  # 后缀表达式结果
            ops_stack = []  # 操作符栈
            infix = self.exp.split(' ')   # 将表达式分割得到单词
            # print(infix)
            for item in infix:
    
                if item in ['+', '-', '×', '÷']:  # 遇到运算符
                    while len(ops_stack) >= 0:
                        if len(ops_stack) == 0:
                            ops_stack.append(item)
                            break
                        op = ops_stack.pop()
                        if op == '(' or ops_rule[item] > ops_rule[op]:
                            ops_stack.append(op)
                            ops_stack.append(item)
                            break
                        else:
                            suffix_stack.append(op)
                elif item == '(':  # 左括号直接入栈
                    ops_stack.append(item)
                elif item == ')':  # 右括号
                    while len(ops_stack) > 0:
                        op = ops_stack.pop()
                        if op == "(":        # 一直搜索到出现“(”为止
                            break
                        else:
                            suffix_stack.append(op)
                else:
                    suffix_stack.append(item)  # 数值直接入栈
    
            while len(ops_stack) > 0:
                suffix_stack.append(ops_stack.pop())
    
            self.re =suffix_stack
            return suffix_stack
    
    
    
    
    

    该代码的思路解释:
    我们实现中缀表达式转化为后缀表达式的思路:
    预处理: 由于题目要求数字与运算符之间要有空格隔开,所以转化过程中需先清除空格
    遍历中缀表达式:

    • 如果遇到操作数,我们就直接将其存入后缀表达式栈suffix_stack[]
    • 如果遇到操作符,则我们将其放入到操作符栈ops_stack中,遇到左括号时我们也将其放入操作符栈ops_stack中
    • 如果遇到一个右括号,则将操作符栈ops_stack元素弹出,将弹出的操作符存入后缀表达式栈suffix_stack直到遇到左括号为止。注意,左括号只弹出并不存入后缀表达式栈suffix_stack。
    • 如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从操作符栈ops_stack中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到操作符栈ops_stack中。有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。
    • 如果我们读到了输入的中缀表达式的末尾,则将ops_stack栈中所有元素依次弹出。

    第三个类:Product类

    功能是生成要求的四则运算式子。

    对于实现生成式子功能,我们把式子的各个要素分别构建方法进行生成,式子可能包含的要素分别为:整数、分数、括号、运算符。

    其中分数的生成是较为复杂的,因为题目要求生成的都是真分数,所以在生成分数后我们有构建了一个方法把假分数转换为带分数

     def DecToStr(self,operArray):
           #分数转化为带分数字符串
            operNum1 = operArray[0]
            operNum2 = operArray[1]
            if operNum2 == 1:
                return operNum1
            if(operNum1 > operNum2):
                temp = int(operNum1/operNum2)
                operNum1 -= (temp*operNum2)
                return str(temp) + "'" + str(operNum1) + "/" + str(operNum2)
            else:
                return str(operNum1) + "/" + str(operNum2)
    

    此外,我们的函数isRepeat(self, express_set, expression)实现了查重,避免生成运算本质相同的式子(egg: 3+2+4 与4+(3+2),像这样就是重复的一种情形 ),不过此算法的时间复杂度较大。

        def isRepeat(self, express_set, expression):
            """
            判断重复方法
            :param
                express_set: 表达式集合
                expression: 生成的表达式
            :return: True or False
            """
            suffixExpression = SuffixExpression(expression)
            target_exp_suffix = suffixExpression.re   # 后缀表达式列表
            binaryTree = BinaryTree()
            target_exp_binary_tree = binaryTree.generateBinaryTree(target_exp_suffix)
            for item in express_set:
                suffixExpression2 = SuffixExpression(item)
                source_exp_suffix = suffixExpression2.re
                source_exp_binary_tree = binaryTree.generateBinaryTree(source_exp_suffix)
                if binaryTree.treeIsSame(target_exp_binary_tree) == binaryTree.treeIsSame(source_exp_binary_tree):
                    return True
            return False
    

    第四个类:Answer类

    功能是:

    1. 调用SuffixExpression类进行计算得出答案文档
    2. 校对提交的答案

    由于需要对两个文档的答案进行校对,所以也有可能出现文档输入不正确的结果,所以设置了两个异常处理,防止打开文件出现问题导致程序出错

        def check_answer(exercisefile, answerfile):
            """
            校对答案
            :param
                exercisefile: 练习题文件
                answerfile: 答案文件
            :return: None
            """
            wrong_num = 0
            correct_num = 0
            exercise_answer = []
            correct_list = []  # 正确题目序号
            wrong_list = []  # 错误题目序号
    
            try:
                with open(exercisefile, 'r', encoding='utf-8') as f:
                    for line in f:
                        # 匹配出正则表达式
                        exp_str = re.findall(r'd+: (.*) = 
    ', line)
                        if exp_str:
                            exp = exp_str[0]
                        else:
                            continue
                        p  = SuffixExpression(exp)
                        exp_value = str(p.suffixToValue())
                        exercise_answer.append(exp_value)
            except IOError:
                print('please check if the path is correct')
    
        # 判断表达式列表是否为空
            try:
                with open(answerfile, 'r', encoding='utf-8') as f:
                    for i, line in enumerate(f):
                        ans_str = re.findall(r'd+: (.*)
    ', line)
                        # 容错
                        if ans_str:
                            ans = ans_str[0]
                        else:
                            continue
                        # 判断是否正确
                        if ans == exercise_answer[i]:
                            correct_num += 1
                            correct_list.append(i + 1)
                        else:
                            wrong_num += 1
                            wrong_list.append(i + 1)
                with open('Grade.txt', 'w+', encoding='utf-8') as f:
                    correct_str = 'Correct: ' + str(correct_num) + ' ' + str(correct_list) + '
    '
                    wrong_str = 'Wrong: ' + str(wrong_num) + ' ' + str(wrong_list)
                    f.write(correct_str)
                    f.write(wrong_str)
            except IOError:
                print('please check if the path is correct')
    

    在读取文件时,采用正则表达式获取所需要的信息:

    ans_str = re.findall(r'd+: (.*)
    ', line)
    
    exp_str = re.findall(r'd+: (.*) = 
    ', line)
    
    

    测试运行

    项目小结

    冯宇航

    对于结对项目的实操是给我们团队编程的一次训练,团队合作可能成员实力有强有弱,这时候能力较强的可能要付出多一点教导能力较弱的成员,两人一个学习一个复习,共同进步。

    方俊涛

    本次结对编程利用python采用了面向对象的思想,进行分模块分类实现。使用到的数据结构有:二叉树,列表,栈。用到的技术有中缀表达式转化为后缀表达式,二叉树判定重复等等。合作方面:开发前二人进行较多的讨论,通过事先调研、二人讨论确定本次开发所需要的技术,并 确定各自任务和合作内容。两人合作可以发现各自无法发现的bug,并互相促进更进进度。结对编程重要的还是交流与互助!

  • 相关阅读:
    数据库优化空间换时间优化
    sql server性能分析查询死锁和阻塞的sql语句
    修改储存过程所有者
    SQL Server 查看数据库基本信息
    SQL语句之普通行列转换
    Ext.Window
    小议操作符“^”与"&"的应用
    数据库设计名值模式(转)
    为数据库建立索引
    sql server性能分析检测数据库阻塞语句
  • 原文地址:https://www.cnblogs.com/shuishangzhizhou/p/13800188.html
Copyright © 2011-2022 走看看