四则运算 Python实现(杨浩政,张兆敏)

四则运算

GitHub仓库:https://github.com/15crmor/Arithmetic

项目要求:

题目:实现一个自动生成小学四则运算题目的命令行程序说明:

说明:

自然数:0, 1, 2, …。

  • 真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
  • 运算符:+, −, ×, ÷。
  • 括号:(, )。
  • 等号:=。
  • 分隔符:空格(用于四则运算符和等号前后)。
  • 算术表达式:

e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),

其中e, e1和e2为表达式,n为自然数或真分数。

  • 四则运算题目:e = ,其中e为算术表达式。

  需求:

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

2.(完成)使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,该参数可以设置为1或其他自然数。

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

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

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

6.(完成)程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+×左右的算术表达式变换为同一道题目。生成的题目存入执行程序的当前目录下的Exercises.txt文件

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

8.(完成)程序应能支持一万道题目的生成。

9.(完成)程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计。统计结果输出到文件Grade.txt,格式如下: 

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

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

 

 

设计实现过程

总体构思:

表达式:

  将整数也看作分数来生成随机数,为Fraction类型,有numerator,denominator两个属性,然后根据运算符个数生成表达式,如:A+B*C/D。

  再转换成逆波兰表达式,然后转化成规范化的二叉树如:随机生成表达式:2*6+5*7 à  逆波兰表达式:26*57*+ , 再转化成二叉树的同时进行规范化并计算结果存入树的value属性中:

  •  二叉树的叶子节点都为数字,非叶子节点都为运算符,当规范化二叉树时,其实是遍历逆波兰式列表中的元素,然后创建一个空树,若遍历到的元素不是运算符,就将数字添加入树的属性,并将树存入列表stack中,若是运算符,则弹出列表中后两个树t2、t1,此时两个树中情况无非是

1.                                                                                                                                           2.                                                                                                                           

                                                                                                    

3.

       

4. 

  •    通过计算比较value值(有运算符则为两数运算后的值,不论是什么运算符,都为t1 op t2,并非左子树 op 右子树)将vlaue较大的设为左子树并计算新的value,若value值相等,则通过判定优先级来确定左右子树。

负数与除数为0:

  负数的产生是由于t1 < t2,同时若在进行减法后的值为0且做除数,则出现除数为0情况,所以通过条件判断舍弃在运算符为“-”时,t1 <= t2 的二叉树

判断表达式是否重复

转换成规范二叉树,然后再以逆波兰格式转成字符串形式,存在一个列表里,每次生成了一个新式子后,就按上面方法生成规范化的二叉树并转成逆波兰形式字符串与列表中所有元素比较。

  •   例如2*6+5*7与7*5+6*2生成的规范化二叉树都为:

                 

其返回的字符串逆波兰式相同,即2*6+5*77*5+6*2是一样的表达式。

主要的类和函数有:

  • class Arith(object)
    •  creat(self, problem_number, r) #  生成四则运算和答案在判重后写入文件
    •    main(self, arith, argv) #  支持命令行输入参数
  • class BinaryTree(object) #  二叉树
    •  out_put_tree(self, tree, s)  #  返回二叉树逆波兰形式的字符串
  • class Caculation(object) 
    •  caulate(self, op, f1, f2)  #  计算f1 op f2 的值(op为运算符)
    •    max(self, num1, num2)  #  比较两分数大小
  • class Check(object)  #  判重
    •  check_tree(self, tree)  #  对二叉树进行判重,若不重复返回True
  • class Compare(object) 
    •  grade(self, exercise_file, answer_file)  #  比较两文件答案并记录
  • class Create(object)  #  生成四则运算表达式
    •    create_operator(self)  #  随机生成运算符 
    •    create_arith(self, r)  #  生成范围在r以内的四则运算表达式
    •    proper_fraction(self, list)  #  将假分数化为带分数
  • class Fractions(object)  #  分数类
  • class CreateTree(object)  #  生成规范二叉树
    •  toRPN(self, list)  #  生成逆波兰表达式
    •    createTree(self, suffix)  #  将逆波兰式转化为规范化二叉树
    •    priority(self, operatorout, operatorin)  #  判定优先级

代码说明:

 生成四则运算表达式:

    def create_arith(self, r):
        x = 0
        list = []
        operator_num = random.randint(1, 3)
        e1 = Create()
        e2 = Create()
        if operator_num == 1:
            list.append(e1.create_number(r))
            list.append(e2.create_operator())
            list.append(e1.create_number(r))
        elif operator_num == 2:
            start = random.randint(0, 2)
            end = 0
            if start > 0:
                end = start + 1
            for i in range(1, 4):
                if i == start:
                    list.append("(")
                list.append(e1.create_number(r))
                if i == end:
                    list.append(")")
                list.append(e2.create_operator())
            list.pop()
        elif operator_num == 3:
            start = random.randint(0, 3)
            end = 0
            if start > 0:
                end = start + 1 + random.randint(0, 1)
                if end >= 4:
                    end = 4
            for i in range(1, 5):
                if i == start:
                    list.append("(")
                list.append(e1.create_number(r))
                if i == end:
                    list.append(")")
                list.append(e2.create_operator())
            list.pop()
        else:
            list.append(e1.create_number(r))
            list.append(e2.create_operator())
            list.append(e1.create_number(r))
        return list

假分数化为带分数:

     # 将表达式假分数转化为带分数
    def proper_fraction(self, list):
        num = 0
        for fract in list:
            if type(fract) == Fraction:
                n1 = fract.numerator
                n2 = fract.denominator
                if n2 == 1:
                    num += 1
                    continue
                elif n1 > n2:
                    sub = int(n1/n2)
                    n1 = n1 % n2
                    list[num] = '%d%s%d/%d' %(sub, '', n1,n2)
            num += 1
        return list

    # 将答案假分数转化为带分数
    def pop_fracte(self, re):
        n1 = re.numerator
        n2 = re.denominator
        if n2 == 1:
            return n1
        elif n1 < n2:
            return re
        else:
            sub = int(n1/n2)
            n1 = n1 % n2
            return '%d%s%d/%d' % (sub, '', n1, n2)

生成逆波兰式:

    def toRPN(self, list):
        right = []
        aStack = []
        position = 0
        while True:
            if self.isOperator(list[position]):
                if list == [] or list[position] == "(":
                    aStack.append(list[position])
                else:
                    if list[position] == ")":
                        while True:
                            if aStack != [] and aStack[-1] != "(":
                                operator = aStack.pop()
                                right.append(operator)
                            else:
                                if aStack != []:
                                    aStack.pop()
                                break
                    else:
                        while True:
                            if aStack != [] and self.priority(list[position], aStack[-1]):
                                operator = aStack.pop()
                                if operator != "(":
                                    right.append(operator)
                            else:
                                break
                        aStack.append(list[position])
            else:
                right.append(list[position])
            position = position + 1
            if position >= len(list):
                break
        while aStack != []:
            operator = aStack.pop()
            if operator != "(":
                right.append(operator)
        return right

将逆波兰式转化成规范化的二叉树:

   def createTree(self, suffix):
        stacks = []

        for i in range(0, len(suffix)):
            tree = BinaryTree()
            ob = suffix[i]
            c = Caculation.Caculation()
            if self.isOperator(ob):
                t2 = BinaryTree()
                t1 = BinaryTree()
                t2 = stacks.pop()
                t1 = stacks.pop()
                if ob == '-' and t1.value <= t2.value:
                    return None
                else:
                    if self.maxTree(t1, t2):
                        tree.set_date(ob)
                        tree.set_left(t1)
                        tree.set_right(t2)
                        tree.set_value(c.caulate(ob, t1.value, t2.value))
                    else:
                        tree.set_date(ob)
                        tree.set_left(t2)
                        tree.set_right(t1)
                        tree.set_value(c.caulate(ob, t1.value, t2.value))
                    stacks.append(tree)
            else:
                tree.set_value(ob)
                tree.set_date(ob)
                stacks.append(tree)
        return tree

 对二叉树判重:

    #  对二叉树进行判重
    def check_tree(self, tree):
        if self.check == []:
            self.check.append(tree)
            return True
        else:
            for i in range(len(self.check)):
                if self.check[i] == tree:
                    return False
        self.check.append(tree)
        return True

二叉树类:

class BinaryTree(object):
    def __init__(self):
        self.date = None
        self.left = None
        self.right = None
        self.value = None

    def tree(self, date, left, right, value):
        self.date = date
        self.left = left
        self.right = right
        self.value = value

    def set_date(self, date):
        self.date = date

    def set_left(self, left):
        self.left = left

    def set_right(self, right):
        self.right = right

    def set_value(self, value):
        self.value = value

    def to_string(self, tree):
        s = ""
        s = self.out_put_tree(tree, s)
        return s

    def out_put_tree(self, tree, s):
        if tree != None:
            s1 = self.out_put_tree(tree.left, s)
            s2 = self.out_put_tree(tree.right, s)
            if type(tree.date) == Fractions.Fractions:
                return str(s1) + str(s2) + str(tree.date.to_string())
            else:
                return str(s1) + str(s2) + str(tree.date)
        return s

分数类:

class Fractions(object):
    def __init__(self):
        self.numerator = None
        self.denominator = None

    def setNumerator(self,numerator):
        self.numerator = numerator

    def setDenominator(self,denominator):
        self.denominator = denominator

    def toString(self):
        a = self.numerator
        b = self.denominator
        return str(Fraction(a, b))

计算参数值:

    def caulate(self, op, f1, f2):
        result = Fractions.Fractions()
        n1 = int(f1.numerator)
        d1 = int(f1.denominator)
        n2 = int(f2.numerator)
        d2 = int(f2.denominator)
        list = []
        if op == '+':
            re = Fraction(n1, d1) + Fraction(n2, d2)

        elif op == '-':
            re = Fraction(n1, d1) - Fraction(n2, d2)

        elif op == '×':
            re = Fraction(n1, d1) * Fraction(n2, d2)

        else:
            re = Fraction(n1, d1) / Fraction(n2, d2)

        return re

文件比较,判断错误答案并记录:

 #  对两个文件中的答案进行比较并记录
    def grade(self, exercise_file, answer_file):
        correct = []
        wrong = []
        co = 0
        wr = 0
        with open(answer_file, 'r') as f1, open(exercise_file, 'r', encoding='utf-8') as f2:
            answers = f2.readlines()
            line = 0
            for r_answers in f1.readlines():
                if answers[line] == r_answers:
                    co += 1
                    correct.append(line+1)
                else:
                    wr += 1
                    wrong.append(line+1)
                line += 1
        with open('gread.txt', 'w') as f3:
            f3.write(f"Correct: {str(co)} ({', '.join(str(s) for s in correct if s not in [None])})" + '\n')
            f3.write(f"Correct: {str(wr)} ({', '.join(str(s) for s in wrong if s not in [None])})" + '\n')
        print("文件比较完成")

生成表达式判重后写入文件:

    # 生成问题和答案在判重后写入文件
    def creat(self, problem_number, r):
        creat_pro = CreatProblem.Create()
        t = BinaryTree.BinaryTree()
        c = Check.Check()
        with open("Exercises.txt", "w") as file1, open("Answer.txt", "w") as file2:
            num = 0
            while num < problem_number:
                arith = creat_pro.create_arith(r)  # 生成四则运算列表
                Ju = CreateTree.Judge()
                al = Ju.toRPN(arith)  # 将列表转换成逆波兰式
                print(al)
                string = creat_pro.to_string(creat_pro.proper_fraction(arith))
                ta = Ju.createTree(al)  # 将逆波兰式生成规范二叉树
                print(t.to_string(ta))
                if ta:
                    val = str(creat_pro.pop_fracte(ta.value))
                    if c.check_tree(t.to_string(ta)):  # 进行判重
                        file1.write("%d. " % (num+1) + string + '\n')
                        file2.write("%d. " % (num+1) + val + '\n')
                        num +=1
        print("四则运算题目生成完毕,数量为%d个" % problem_number)

命令行程序入口:

    # 支持命令行键入参数
    def main(self, arith, argv):
        problem_number = None
        num_range = None
        exercise_file = None
        answer_file = None
        try:
            opts, args = getopt.getopt(argv, "n:r:e:a:")
        except getopt.GetoptError:
            print('Error: arith.py -n <problem_number> -r <num_range>')
            print('   or: test_arg.py -e -e <exercisefile>.txt -a <answerfile>.txt')
            sys.exit(2)

        for opt, arg in opts:
            if opt in("-n"):
                problem_number = int(arg)
            elif opt in ("-r"):
                num_range = int(arg)
            elif opt in("-e"):
                exercise_file = arg
            elif opt in("-a"):
                answer_file = arg
        if problem_number and num_range:
            arith.creat(problem_number, num_range)
        elif exercise_file and answer_file:
            compare = Compare.Compare()
            compare.grade('ReAnswer.txt', 'Answer.txt')
        else:
            print('Error: arith.py -n <problem_number> -r <num_range>')
            print('   or: test_arg.py -e -e <exercisefile>.txt -a <answerfile>.txt')

测试运行

参数错误提示

生成10道题的题目与答案

文件答案比较

生成一万道题目

以下为一万到题目和答案的链接

点此查看一万条表达式

 点此查看表达式答案

 PSP

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

 60

 80

· Estimate

· 估计这个任务需要多少时间

 50

 50

Development

开发

 900

800

· Analysis

· 需求分析 (包括学习新技术)

 40

 80

· Design Spec

· 生成设计文档

 60

 60

· Design Review

· 设计复审 (和同事审核设计文档)

 20

 30

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

 30

 30

· Design

· 具体设计

 60

 180

· Coding

· 具体编码

 500

 800

· Code Review

· 代码复审

 100

 120

· Test

· 测试(自我测试,修改代码,提交修改)

 120

150

Reporting

报告

 60

 90

· Test Report

· 测试报告

 30

 30

· Size Measurement

· 计算工作量

 20

 30

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

 20

 20

合计

 2070

 2550

 

 

项目小结:

      在本次结点编程四则运算项目中,与杨浩政同学一起组队讨论写代码。首先呢,我们一起讨论如何实现这个项目,共同讨论书写设计文档。在写设计文档的过程中,我们很多想法都不一样,由于我的编程水平比较低,缺乏经验,一般来说,都是以杨浩政同学的想法为主,我的想法为辅。在交流设计思路的过程中,我们两人的思维也会碰撞出更好的解决思路。在双方都认为自己的想法比较好时,会各自说出自己想法的优点,然后再共同思考哪个实现途径更优。最大的感触是:多听听不同的想法,会使自己的思维更开阔。(来自张兆敏同学的小结)

  本次结对编程我主要负责代码实现,这次编程对我的帮助很大,让我明白了只有思路足够清晰才能更快更好地实现功能,在拿到项目后一定要多思考如何实现更简洁方便,而不是边写边想,到最后不停修改。与队友的讨论也启发了我一些思路,只要思路上能找到合适的方法,那么实现起来就会容易快速很多。(来自杨浩政的小结)

猜你喜欢

转载自www.cnblogs.com/yhz-zero/p/9727370.html
今日推荐