• 利用Python实现四则运算表达式生成程序


    一. 项目基本信息

    项目成员:梁华超、林贤杰

    项目仓库:Github

    二. PSP2.1表格

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

    三. 设计实现过程及代码说明

    项目文件结构如下:

    模块功能
    main.py 主函数
    answer.py 计算答案和校对答案
    exp_generate.py 表达式生成
    suffix_expression.py 将中缀表达式转成后缀表达式和求值

    1.分析与设计

    本设计涉及到的基本数据类型和表达式有栈,二叉树,逆波兰表达式(后缀表达式)

    表达式生成 :

    仔细分析表达式有如下特点:

    • 运算数的个数比运算符多一
    • 被除数不能为0
    • 两个操作数不需要加括号

    利用python中字符串列表来存储四则表达式,新建一个列表,大小为运算符个数+运算数,然后循环遍历此列表,在偶数位置插入随机的运算数,在奇数位置插入随机的运算符。

    括号的插入:

    左括号的插入位置是从0到操作数个数的一半之间的一个随机数,右边括号为左括号的位置+1到操作数个数的一半+1。

    计算答案:

    将中缀表达式转为后缀表达式,再进行求值

    生成的二叉树如下这样:

    2.具体实现

    (1) 表达式生成关键代码

    需要注意的是除号后面的运算符不能为0,如果生成的是0,即重新生成插入,直到生成不为0的运算符为止。

    while i < exp_num:
                random_num_operation = randint(1, config.max_num_of_oper)
                is_need_parenteses = 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.generate_operand(randint(0,3), config.num_range))
                        if j > 1 and exp[j-1] == '÷' and exp[j] == '0':
                            while True:
                                exp[j-1] = self.generate_operation()
                                if exp [j-1] == '÷':
                                    continue
                                else:
                                    break
                    else:
                        #生成运算符
                        exp.append(self.generate_operation())
                
                #判断是否要括号
                if is_need_parenteses and number_of_oprand != 2:
                    expression = " ".join(self.generate_parentheses(exp, number_of_oprand))
                else:
                    expression = " ".join(exp)
                
                #判断是否有重复
                if self.is_repeat(exp_list, expression) or suffix_to_value(to_suffix(expression)) == False:
                    continue
                else:
                    exp_list.append(expression)
                    i = i + 1

    (2)插入括号代码逻辑

    如果生成的括号表达式形如 (1 + 2/3 + 3),则认为是没有意义的括号,需要重新插入。

    if exp:
                exp_length = len(exp)
                left_position = randint(0,int(num/2))
                right_position = randint(left_position+1, int(num/2)+1)
                mark = -1
                for i in range(exp_length):
                    if exp[i] in ['+', '-', 'x', '÷']:
                        expression.append(exp[i])
                    else:
                        mark += 1
                        if mark == left_position:
                            expression.append('(')
                            expression.append(exp[i])
                        elif mark == right_position:
                            expression.append(exp[i])
                            expression.append(')')
                        else:
                            expression.append(exp[i])
            #如果生成的括号表达式形如 (1 + 2/3 + 3) 则重新生成
            if expression[0] == '(' and expression[-1] ==')':
                expression = self.generate_parentheses(exp, number_of_oprand)
                return expression

    (3)中缀转后缀和求值

    中缀表达式转后缀表达式的逻辑:

    1. 初始化两个栈,分为运算符栈和后缀表达式栈,遍历表达式列表,如果遇到运算符:

      a. 如果运算符栈为空,则直接入栈

      b. 如果运算符栈不为空,则取出栈顶top元素

      • 如果栈顶top元素是左括号或者算术优先级高于栈顶top元素,那么就直接入栈

      • 否则就入栈后缀表达式栈

    2. 如果遇到左括号:

      • 左括号直接入运算符栈

    3. 如果遇到右括号:

      • 如果运算符栈不为空,那么直接出栈,添加到后缀表达式栈,直到遇到左括号

    4. 遇到运算数直接入后缀表达式栈

    suffix_stack = []  #后缀表达式结果
        ops_stack = []  #操作符栈
        infix = exp.split(' ')
        #print(infix)
        for item in infix:
            if item in ['+', '-', 'x', '÷']: #遇到运算符
                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())

    四. 运行测试

    文件说明:

    文件说明
    Answer.txt 生成表达式答案文件
    Exercises.txt 生成表达式存储的文件
    Grade.txt 题目对错数量统计文件

     

    结果:

    效能分析:

    • 因为涉及到二叉树递归等操作,所以会有很多时间和空间的开销
    • IO读写也影响运算的时间

    五. 项目总结

    这次结对编程中,我和林贤杰一起深入分析项目的需求分析,找到实现需求的具体思路,设计具体实现的过程,我负责编码,林贤杰同学在旁边观察协助。在此过程中,我们也遇到了一些问题,也找到了解决的思路。总之,在结对编程中有很大的收获,实现1+1 > 2 。

  • 相关阅读:
    csu1022 菜鸟和大牛 dp
    POJ 1001 Exponentiation
    KMPmatch 字符串模式匹配
    UVaOJ458 The Decoder
    UVaOJ 10300 Ecological Premium
    MLE: 找出出现偶数次的那个数
    csu 1207: 镇管的难题
    csu 1079
    UVaOj 494 Kindergarten Counting Game
    轻松掌握Ajax.net系列教程七:使用ModalPopupExtender
  • 原文地址:https://www.cnblogs.com/tworld/p/9709610.html
Copyright © 2020-2023  润新知