结对项目(王浩忠 曾铭鸿)

学生个人信息

成员 学号
王浩忠 3121005099
曾铭鸿 3121005101

作业信息

这个作业属于哪个课程 软件工程
这个作业要求在哪里 结对项目
这个作业的目标 实现一个自动生成小学四则运算的程序

GitHub地址

https://github.com/Binezis/Pair_Program

PSP表格

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

效能分析

改进程序性能的时间因个体差异而异,但以下是可能的性能改进思路,以及消耗最多时间的函数:

  • 性能瓶颈函数:bracket_answer 函数可能是你的代码中最复杂的函数之一,负责计算不同级别括号的表达式。这个函数可能会消耗大量时间,特别是在处理深度嵌套表达式时。考虑优化这个函数以获得更好的性能,例如实施更高效的表达式计算算法。
  • 分数处理:使用 Fraction 类处理分数可能会导致计算量很大,尤其是在处理大数时。您可能希望探索优化分数计算的方式,例如简化分数和减少不必要的计算。
  • 文件操作:读写文件(Exercise.txt、Answer.txt 和 Grade.txt)也会影响性能。确保您有效地管理文件 I/O,例如为写入缓冲数据并逐行读取,而不是一次读取整个文件。
  • 正则表达式模式:正则表达式对于大型输入可能很慢。如果可能的话,尽量减少正则表达式的使用,并在解析和处理表达式时使用更简单的字符串操作。
  • 算法改进:考虑在代码中进行算法改进。例如,如果您正在检查重复表达式,请考虑更高效的数据结构或算法来执行此任务。
  • 并行处理:根据您的硬件和计算性质,您可以探索并行处理,以同时执行计算,这可能会带来显著的性能提升。
  • 缓存:如果某些计算重复进行,考虑对结果进行缓存,以避免冗余计算。

设计实现过程

代码是一个用Python编写的程序,用于生成数学四则运算练习题以及相应的答案,并具有检查答案正确性的功能。以下是代码的设计和组织的详细说明:

导入模块

代码以导入语句开始,导入了必要的模块,如argparse、re、fractions中的Fraction,以及random中的randint。

Operation类

这个类是代码的主要组件。
它被设计用于生成数学四则运算练习题,将它们写入"Exercise.txt"文件,并将相应的答案写入"Answer.txt"文件。

  • __init__方法初始化了练习题数量和操作数范围的默认值。
  • operation方法负责生成练习题和答案。它使用了几个辅助方法,并处理了异常,如ZeroDivisionError。
  • combine方法生成带有随机运算符和操作数的数学表达式。
  • is_proper和get_proper_fraction方法负责随机生成真分数。
  • to_fraction方法将不恰当的分数转换为带分数。
  • four_operator方法选择一个随机的数学运算符。
  • bracket_insert方法根据指定的括号参数在表达式中插入括号。
  • get_answer方法计算给定表达式的答案。
  • bracket_answer方法计算带有括号的表达式的答案。

实用函数

  • is_same检查两个表达式是否等价。
  • check检查新表达式是否是唯一的,不在先前表达式列表中。
  • out_grade读取练习题和答案文件,检查正确性,并返回正确和错误答案的摘要。
  • add_parm是用于解析命令行参数的实用函数。
  • main是程序的入口点。它使用argparse来解析命令行参数,并执行适当的操作(生成练习题或检查答案)。

if name == 'main'

这个代码块确保在将脚本作为主程序运行时执行main函数。

该代码按照类的结构组织,Operation类处理生成数学练习题的任务,各种辅助函数协助完成这个任务。main函数是程序的入口点,允许根据命令行参数选择生成练习题或检查答案。

流程图

graph TD A[开始] --> B[生成数量和范围] B --> C[生成练习题和答案] C --> D[输出练习题和答案文件] D --> E[结束] B --> F[检查答案文件] F --> G[输出评分结果] G --> E E --> H[输出正确和错误结果]

代码说明

主要代码

class Operation:

    def __init__(self):
        self.number = 10
        self.value = 10

    def operation(self):
        """
        生成四则运算函数,调用相关函数实现生成
        :return: None
        """
        f_exercise = open('Exercise.txt', 'a+', encoding='utf-8')
        f_answer = open('Answer.txt', 'a+', encoding='utf-8')
        f_exercise.seek(0)
        f_answer.seek(0)
        f_exercise.truncate()
        f_answer.truncate()
        count = 0
        topic = []
        ans = []
        while True:
            try:
                exercise_list, answer = self.combine()  # 控制运算符数量
            except ZeroDivisionError:  # 当0位除数 和 负数情况
                continue
            # True表示检查后无重复
            if check(exercise_list, answer, topic, ans):
                topic.append(str("".join(exercise_list)))
                f_exercise.write("题目" + str(count + 1) + ": " + ' '.join(exercise_list) + ' =\n')
                if re.search('/', answer):
                    d, n = answer.split('/')
                    ans.append(answer)
                    if int(d) > int(n):
                        answer = to_fraction(answer)
                f_answer.write("答案" + str(count + 1) + ": " + answer + '\n')
                count += 1
                if count == self.number:
                    break
        f_exercise.close()
        f_answer.close()
  • 思路:首先实现输出固定的运算式到文件中然后通过不断加功能实现随机出题,计算答案。具体函数功能在设计实现过程已经讲述。

  • 注释说明:在每个函数都有函数注释,部分关键行也有注释。

测试运行

测试代码test.py中共设置11个测试用例

1. test_main_n_r:测试-n 100 -r 100 情况下的输出结果

    @mock.patch("Myapp.add_parm")
    def test_main_n_r(self, mock_args):
        mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=100, sum=100)
        main()
        exercise_file = open("Exercise.txt", "r", encoding='utf-8')
        exercise_lines = exercise_file.readlines()
        answer_file = open("Answer.txt", "r", encoding='utf-8')
        answer_lines = answer_file.readlines()
        exercise_file.close()
        answer_file.close()
        self.assertEqual(len(exercise_lines), 100)
        self.assertEqual(len(answer_lines), 100)
  • 通过断言是否产生了100行数据判断是否成功。

2. test_main_e_a:测试-e Exercise.txt -a Answer.txt 情况下的输出结果

    @mock.patch("Myapp.add_parm")
    def test_main_e_a(self, mock_args):
        mock_args.return_value = Namespace(answer_file="Answer.txt", exercise_file="Exercise.txt", range=None, sum=None)
        main()
        grade_file = open("Grade.txt", "r", encoding='utf-8')
        grade_lines = grade_file.readlines()
        grade_file.close()
        self.assertEqual(grade_lines[1], "Wrong:0 ()")
  • 由于Answer.txt里面全是上一步生成的正确答案,所以通过断言错误数为0,判断是否成功。

3. test_main_none:测试无参数情况下的输出结果

    @mock.patch("Myapp.add_parm")
    def test_main_none(self, mock_args):
        mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=None, sum=None)
        self.assertEqual(main(), ValueError)
  • 通过断言异常断定参数输入错误。

4. test_add_parm:测试添加命令行参数结果

    def test_add_parm(self):
        self.assertEqual(add_parm(), Namespace(answer_file=None, exercise_file=None, range=None, sum=None))
  • 通过断言参数列表来判断正确。

5. test_out_grade:测试判定答案中的对错并进行数量统计情况

    def test_out_grade(self):
        grade = out_grade("Exercise.txt", "Answer.txt")
        grade_split = grade.split('\n')
        self.assertEqual(grade_split[1], "Wrong:0 ()")
        grade = out_grade("Exercise.txt", "Wrong.txt")
        grade_split = grade.split('\n')
        self.assertEqual(grade_split[0], "Correct:0 ()")
  • 由于Wrong.txt里面答案全是-1,所以通过断言正确数为0来判断正确。

6. test_check:测试检查运算式是否相同结果

    def test_check(self):
        b = check(['2', 'x', "1'1/2"], '3', ["1'1/2x2"], ['3'])
        self.assertEqual(b, False)
  • 由于两个式子相同,所以不通过检查,断言False。

7. test_is_same:测试运算符字符串是否相同结果

    def test_is_same(self):
        b = is_same("(4/5+3/5)x1/2", ["1/2x(3/5+4/5)"])
        self.assertEqual(b, True)
  • 由于两个式子相同,判断相同,断言相同。

8. test_to_fraction:测试假分数转换为真分数是否正确

    def test_to_fraction(self):
        fraction = to_fraction("89/55")
        self.assertEqual(fraction, "1'34/55")
        fraction = to_fraction("10/5")
        self.assertEqual(fraction, "2")
        fraction = to_fraction("5'10")
        self.assertEqual(fraction, ValueError)
  • 断言假分数对应跟真分数是否相同。

9. test_bracket_answer:测试计算运算式时运算式为空情况下

    def test_bracket_answer(self):
        self.assertEqual(bracket_answer("", "", None), None)
  • 断言运算式为空时,答案为空。

10. test_get_proper_fraction:测试产生真分数是否正确

    def test_get_proper_fraction(self):
        operator = Operation()
        proper_fraction = operator.get_proper_fraction()
        fraction_split = proper_fraction.split('/')
        self.assertLess(fraction_split[0], fraction_split[1])
  • 断言真分数分子小于分母。

11. test_10000_n:测试产生10000道题目

    @mock.patch("Myapp.add_parm")
    def test_10000_n(self, mock_args):
        mock_args.return_value = Namespace(answer_file=None, exercise_file=None, range=5, sum=10000)
        main()
        exercise_file = open("Exercise.txt", "r", encoding='utf-8')
        exercise_lines = exercise_file.readlines()
        answer_file = open("Answer.txt", "r", encoding='utf-8')
        answer_lines = answer_file.readlines()
        exercise_file.close()
        answer_file.close()
        self.assertEqual(len(exercise_lines), 10000)
        self.assertEqual(len(answer_lines), 10000)
  • 断言是否产生了10000行数据。

测试结果

11个测试全部执行成功,共耗时为3秒871毫秒,其中test_10000-n为生成10000条数据,所以耗时最长为3秒311毫秒。

测试覆盖率

所有代码均覆盖到。

项目小结

  • 1.这个项目采用了Python语言,成功实现了所有预期的功能。

  • 2.这是我们第一次进行结对编程项目,初期的效率并不高。然而,通过积极的沟通和磨合,我们逐渐产生了默契,效率也逐步提高,从而成功地解决了所有问题。

  • 3.回过头来看,我们存在诸多问题,但我们两人始终积极交流、按照要求完成工作并全力投入,使得本次项目得以顺利完成。

  • 4.与之前的个人编程相比,本次编程在开发质量和时间上有明显的改善和优化。

posted @ 2023-09-28 09:07  王浩忠  阅读(66)  评论(0)    收藏  举报