结对作业

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

这个作业属于哪个课程 软件工程
这个作业要求在哪里 结对项目
这个作业的目标 熟悉结对开发的流程并完成相应的结对项目

1.GitHub及项目成员

GitHub:https://github.com/DraymondKris/DraymondKris/tree/master/arithmetic
项目成员:

柳瓒 3119005427
卢浩华 3119005428

2.PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计这个任务需要多少时间 1560 1790
Development 开发 1290 2080
· Analysis · 需求分析 (包括学习新技术) 480 480
· Design Spec · 生成设计文档 30 30
· Design Review · 设计复审 30 30
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 40
· Design · 具体设计 60 60
· Coding · 具体编码 600 1200
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 120 180
Reporting 报告 150 190
· Test Repor · 测试报告 90 120
· Size Measurement · 计算工作量 20 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 40 60
· 合计 1590 2300

3.效能分析

  • 整体函数调用图
  • 图形化界面调用

    可以看出,耗费时间最多的函数是调用的tkinter包内函数,无法改进
  • 不通过图形化界面调用时,出题功能的性能分析


    当只出10道题时,耗费时间最多的自定义函数是create_express,既出题函数
def create_express(r):
    symbol_num = random.randint(1, 3)  # 标点符号个数
    express_list = []
    number_count = 0
    symbol_count = 0
    for num in range(0, 2 * symbol_num + 1):
        if num <= 1:
            number_count += 1
            data = create_data(r)
            express_list.append(str(data))
        else:
            if number_count <= symbol_count + 1:
                number_count += 1
                data = create_data(r)
                express_list.append(str(data))
            elif number_count < symbol_num + 1:
                symbol_or_data = random.choice([0, 1])
                if symbol_or_data == 1:
                    number_count += 1
                    data = create_data(r)
                    express_list.append(str(data))
                else:
                    symbol_count += 1
                    symbol = create_symbol()
                    express_list.append(symbol)
            else:
                symbol_count += 1
                symbol = create_symbol()
                express_list.append(symbol)
    express_tree = create_express_tree(express_list)
    express = []
    express_tree.midshow(express_tree.root, express)
    del_unuseful(express)
    express_str = ' '.join(express)
    express_str = express_str + ' = '
    return express_tree, express_list, express_str

当出题数达到100时

还是create_express函数耗时最多,但是判断两个式子相等的函数耗费的时间明显增高

def judge_same_tree(tree1, tree2):
    if (tree1 is None and tree2 is not None) or (tree1 is not None and tree2 is None):
        return False
    if tree1 is None and tree2 is None:
        return True
    # 如果两棵树该结点的数据都相同且其a树的左子树等于b树的左子树且a树的右子树等于b树的右子树或者a树的左子树等于b树的右子树且a树的右子树等于b树的左子树时,认定两棵树相同
    if tree1.elem != tree2.elem:
        return False
    if (judge_same_tree(tree1.left, tree2.left) is False and judge_same_tree(tree1.right, tree2.right) is False) and (
            judge_same_tree(tree1.right, tree2.left) is False and judge_same_tree(tree1.left, tree2.right) is False):
        return False
    return True
  • 改卷功能的性能分析


    该功能耗费时间最多的是calculate函数
def calculate(houzui):
    num = []
    for j in houzui:
        if judge_symbol(j) is False:
            num.append(str_to_num(j))
        else:
            num2 = num.pop()
            num1 = num.pop()
            if j == '+':
                num.append(num1 + num2)
            elif j == '-':
                if num2 > num1:
                    return -1
                else:
                    num.append(num1 - num2)
            elif j == '×':
                num.append(num1 * num2)
            else:
                if num2 == 0:
                    return -1
                else:
                    num.append(Fraction(num1, num2))
    if num[0] > 1 and '/' in str(num[0]):
        return turn_fraction(num[0])
    return num[0]

4.设计实现过程

  • 需求图:

  • 程序执行过程:

  • 答案文件判错过程

  • 共有3个模块,每个模块的功能如下

    • creact_expression.py:生成一个分数,把假分数转换为带分数,选择3个以内的运算符号,并通过后缀表达式构建表达式二叉树

      • create_fraction(): 生成分数
      • turn_fraction():将假分数转换为带分数
      • create_symbol():创建运算符
      • tree_Node(): 构建二叉树结点
      • class tree(): 构建二叉树
      • create_express_tree():通过后缀表达式构建表达式二叉树
    • calculate.py:检查相关的符号规则,运算规则:如不能出现负数

      • judge_symbol():判断字符是否为运算符
      • calculate():运算规则
    • Myapp.py:生成两个二叉树,用于检查题目是否重复,并用于输入输出的对接,按照格式处理生成的题目和答案,对比已知题目和答案;

      • judge_same_tree():判断题目是否相同
      • create_text():输入参数并指定输出文本
      • mark_grade():判断答案对错
      • class UI(object):UI界面

5.代码说明

  • 项目结构图:

  • 进行运算,检查相关的符号规则,运算规则:不能出现负数,除数不能为0
def calculate(houzui):
    num = []
    for j in houzui:
        if judge_symbol(j) is False:
            num.append(str_to_num(j))
        else:
            num2 = num.pop()
            num1 = num.pop()
            if j == '+':
                num.append(num1 + num2)
            elif j == '-':
                if num2 > num1:
                    return -1
                else:
                    num.append(num1 - num2)
            elif j == '×':
                num.append(num1 * num2)
            else:
                if num2 == 0:
                    return -1
                else:
                    num.append(Fraction(num1, num2))
    if num[0] > 1 and '/' in str(num[0]):
        return turn_fraction(num[0])
    return num[0] creact_expression.py:
  • 生成一个分数
def create_fraction(r):
    denominator = random.randint(1, r)  # 生成分母
    numerator = random.randint(1, denominator * r)  # 生成分子
    fraction = Fraction(numerator, denominator)  # 生成假分数
    if '/' in str(fraction):
        if numerator > denominator:
            return turn_fraction(denominator, numerator)
    return str(fraction)
  • 将假分数转换为真分数

def turn_fraction(denominator, numerator):
    integer = numerator // denominator
    real_fraction = Fraction(numerator - integer * denominator, denominator)
    return str(str(integer) + '`' + str(real_fraction))

  • 通过构建二叉树进行题目的生成
# 构建二叉树结点
class tree_Node(object):
    def __init__(self, elem):
        self.elem = elem
        self.left = None
        self.right = None
        self.result = 0


# 构建二叉树
class tree(object):

    def __init__(self, root):
        root = tree_Node(root)
        self.root = root

    # 创建树形左子树
    def create_left_tree(self, left_tree):
        temp = self.root
        if isinstance(left_tree, tree):
            temp.left = left_tree.root
            temp.result = left_tree.root.result

    # 创建数据左子树
    def create_left_children(self, data):
        newNode = tree_Node(data)
        temp = self.root
        while temp.left is not None:
            temp = temp.left
        temp.left = newNode
        newNode.result = str_to_num(data)
        temp.result = newNode.result

    # 创建树形右子树
    def create_right_tree(self, right_tree):
        temp = self.root
        if isinstance(right_tree, tree):
            temp.right = right_tree.root
            if temp.elem == '+':
                temp.result += right_tree.root.result
            elif temp.elem == '-':
                temp.result = temp.result - right_tree.root.result
                if temp.result < 0:
                    temp.result = -1
            elif temp.elem == '×':
                temp.result *= right_tree.root.result
            elif temp.elem == '÷':
                if right_tree.root.result <= 0:
                    temp.result = -1
                else:
                    temp.result = Fraction(temp.result, right_tree.root.result)

    # 创建数据右子树
    def create_right_children(self, data):
        newNode = tree_Node(data)
        temp = self.root
        while temp.right is not None:
            temp = temp.right
        temp.right = newNode
        newNode.result = str_to_num(data)
        if temp.elem == '+':
            temp.result += newNode.result
        elif temp.elem == '-':
            temp.result = temp.result - newNode.result
            if temp.result < 0:
                temp.result = -1
        elif temp.elem == '×':
            temp.result *= newNode.result
        elif temp.elem == '÷':
            if newNode.result <= 0:
                temp.result = -1
            else:
                temp.result = Fraction(temp.result, newNode.result)

    # 中序遍历
    def midshow(self, new_tree, express):
        if new_tree is None:
            return
        if (new_tree.left is not None and new_tree.right is not None):
            express.append('(')
        self.midshow(new_tree.left, express)
        express.append(new_tree.elem)
        self.midshow(new_tree.right, express)
        if (new_tree.left is not None and new_tree.right is not None):
            express.append(')')
        return express

# 通过后缀表达式构建表达式二叉树
def create_express_tree(express_list):
    stack = []
    for str_temp in express_list:
        if judge_symbol(str_temp) is True:
            tree_temp = tree(str_temp)
            if len(stack) <= 0:
                print('错误!')
                return

            right_node = stack.pop()
            left_node = stack.pop()
            if isinstance(left_node, tree):
                tree_temp.create_left_tree(left_node)
            else:
                tree_temp.create_left_children(left_node)
            if isinstance(right_node, tree):
                tree_temp.create_right_tree(right_node)
            else:
                tree_temp.create_right_children(right_node)

            stack.append(tree_temp)
        else:
            stack.append(str_temp)
    return stack.pop()


def create_express(r):
    symbol_num = random.randint(1, 3)  # 标点符号个数
    express_list = []
    number_count = 0
    symbol_count = 0
    for num in range(0, 2 * symbol_num + 1):
        if num <= 1:
            number_count += 1
            data = create_data(r)
            express_list.append(str(data))
        else:
            if number_count <= symbol_count + 1:
                number_count += 1
                data = create_data(r)
                express_list.append(str(data))
            elif number_count < symbol_num + 1:
                symbol_or_data = random.choice([0, 1])
                if symbol_or_data == 1:
                    number_count += 1
                    data = create_data(r)
                    express_list.append(str(data))
                else:
                    symbol_count += 1
                    symbol = create_symbol()
                    express_list.append(symbol)
            else:
                symbol_count += 1
                symbol = create_symbol()
                express_list.append(symbol)
    express_tree = create_express_tree(express_list)
    express = []
    express_tree.midshow(express_tree.root, express)
    del_unuseful(express)
    express_str = ' '.join(express)
    express_str = express_str + ' = '
    return express_tree, express_list, express_str
  • 生成两个二叉树,用于检查题目是否重复
# 构建二叉树结点
class new_tree_Node(object):
    def __init__(self):
        self.elem = None
        self.left = None
        self.right = None

# 空节点添加数据为-1
def add_none_data(old_tree):
    new_tree = new_tree_Node()
    if old_tree is None:
        new_tree.elem = -1
        new_tree.left = None
        new_tree.right = None
        return new_tree
    new_tree.elem = old_tree.result
    new_tree.left = add_none_data(old_tree.left)
    new_tree.right = add_none_data(old_tree.right)
    return new_tree


# 判断两个题目是否相同
def judge_same_tree(tree1, tree2):
    if (tree1 is None and tree2 is not None) or (tree1 is not None and tree2 is None):
        return False
    if tree1 is None and tree2 is None:
        return True
    # 如果两棵树该结点的数据都相同且其a树的左子树等于b树的左子树且a树的右子树等于b树的右子树或者a树的左子树等于b树的右子树且a树的右子树等于b树的左子树时,认定两棵树相同
    if tree1.elem != tree2.elem:
        return False
    if (judge_same_tree(tree1.left, tree2.left) is False and judge_same_tree(tree1.right, tree2.right) is False) and (
            judge_same_tree(tree1.right, tree2.left) is False and judge_same_tree(tree1.left, tree2.right) is False):
        return False
    return True
  • 判断题目与答案的对错
def mark_grade(e, a):
    correct_num = 0
    correct = []
    wrong_num = 0
    wrong = []
    f1 = open(e, 'r', encoding='utf-8')
    f2 = open(a, 'r', encoding='utf-8')
    list1 = f1.readlines()
    list2 = f2.readlines()
    f1.close()
    f2.close()
    for i in range(len(list2)):
        express = list1[i].split('. ')[1].split('=')[0]
        houzui = express_to_houzui(express)
        answer = calculate.calculate(houzui)
        if len(list2[i].split('. ')) < 2:
            answer_user = '-1'
        else:
            answer_user = list2[i].split('. ')[1].split('\n')[0]
        if str(answer) == answer_user:
            correct.append(i + 1)
            correct_num += 1
        else:
            wrong.append(i + 1)
            wrong_num += 1
    if len(list2) < len(list1):
        num = len(list1) - len(list2)
        for i in range(num):
            wrong.append(len(list2) + i + 1)
            wrong_num += 1
    f3 = open('Grade.txt', 'a', encoding='utf-8')
    f3.write(' ')
    f3.close()
    f3 = open('Grade.txt', 'r+')
    f3.truncate()
    f3.close()
    f3 = open('Grade.txt', 'a', encoding='utf-8')
    f3.write('Correct: ')
    f3.write(str(correct_num) + '(')
    for j in range(len(correct)):
        f3.write(str(correct[j]))
        if j < len(correct) - 1:
            f3.write(',')
    f3.write(')\n')
    f3.write('Wrong: ')
    f3.write(str(wrong_num) + '(')
    for j in range(len(wrong)):
        f3.write(str(wrong[j]))
        if j < len(wrong) - 1:
            f3.write(',')
    f3.write(')\n')
    f3.close()

6.测试运行

  • 1.使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10 将生成10个题目。

  • 2.使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10,将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信 息

    • 这里我们使用的是界面输入的方式来输入n和r的参数
    • 生成的题目在Exercise.txt文件里
    • 当没有给定的参数或给定的参数不是自然数时会报错并给出帮助信息
      • UI生成界面
      • 输入异常

  • 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt

    • 批改界面
    • 在Answers.txt 文件改变第2题答案(写错答案),并将第5题的答案置空(没有写答案的情况),结果如下:


  • 10000条题目

7.单元测试

import unittest
import calculate
import creact_expression
import Myapp
from fractions import Fraction


class MyTest(unittest.TestCase):
    #测试将假分数转换为真分数
    def test1(self):
        self.assertEqual(creact_expression.turn_fraction(3,10),'3`1/3')

    #测试字符串转换为分数
    def test2(self):
        self.assertEqual(creact_expression.str_to_num('3`1/3'),Fraction(10,3))

    #测试生成表达式
    def test3(self):
        tree,list,expression=creact_expression.create_express(5)
        print(expression)

    #测试计算过程
    def test4(self):
        self.assertEqual(calculate.calculate(['1','3','3','4','×','+','×']),15)

    #测试e1-e2出现负数的情况
    def test5(self):
        self.assertEqual(calculate.calculate(['3','7','-']),-1)

    #测试除数为0的情况
    def test6(self):
        self.assertEqual(calculate.calculate(['3','4','4','-','÷']),-1)

    #测试两个式子相等
    def test7(self):
        self.assertTrue(Myapp.judge_same_tree(Myapp.add_none_data(creact_expression.create_express_tree(['1','2','+']).root),Myapp.add_none_data(creact_expression.create_express_tree(['2','1','+']).root)))

    # 测试两个式子不相等
    def test8(self):
        self.assertFalse(Myapp.judge_same_tree(Myapp.add_none_data(creact_expression.create_express_tree(['2', '2', '+']).root),Myapp.add_none_data(creact_expression.create_express_tree(['2', '1', '+']).root)))

    #测试判断是否为运算符
    def test9(self):
        self.assertTrue(creact_expression.judge_symbol('+'))

    #测试判断不是运算符
    def test10(self):
        self.assertFalse(creact_expression.judge_symbol('1`5/6'))

if __name__ == '__main__':
    unittest.main()

单元测试结果

8.项目小结

  • 卢浩华:这是我第一次与人合作共同完成一个项目的编写,鉴于我个人的代码编写能力不足,因此在程序编写这一块更多的仰仗另一位合作伙伴,我更多的负责编写相关的文档及资料资料整理,在本次项目中,我也学会了很多知识,对项目合作有了更深入的了解,对博客的编写也更加熟练。
  • 柳瓒:这是一次较好的合作体验,相比于孤军奋战,两人合作能更好地对问题进行讨论,从而找出更好的解决方法,通过查阅资料和推理,发现通过二叉树,无论是构建表达式还是判断重复亦或是计算都很方便,因此整个程序的核心数据结构就是二叉树,但是对于整个项目的整体结构,在一开始对于每一个函数,每一个模块并不能很好的先进行构思,而是在编写的过程中,出现问题再构思函数模块并解决。
posted @ 2021-10-24 23:28  晴天亦会下雨  阅读(39)  评论(0编辑  收藏  举报