姓名与学号
蔡坤泰 3121005073
与其他班级学生合作
作业要求
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
GitHub地址
https://github.com/CaiKunTai/CaiKunTai/tree/main/%E7%BB%93%E5%AF%B9%E9%A1%B9%E7%9B%AE
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 600 | 540 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 60 | 30 |
· Design Review | · 设计复审(和同事审核设计文档) | 60 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 120 | 30 |
· Coding | · 具体编码 | 120 | 150 |
· Code Review | · 代码复审 | 30 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 90 |
Reporting | 报告 | 150 | 120 |
· Test Repor | · 测试报告 | 60 | 30 |
· Size Measurement | · 计算工作量 | 60 | 60 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 780 | 680 |
结果展示
- 生成十道题目后的结果
- 修改前三道题目答案后检验的结果
- 生成一万道题目后的结果
效能分析
设计实现过程
1.导入模块
导入必要模块,包括argparse、random和Fraction。这些模块用于处理命令行参数、生成随机数和处理分数。
2. 函数与关系
- generate_number函数生成一个随机整数,用于创建数学表达式的操作数。
- generate_fraction函数生成一个随机分数,用于创建数学表达式的操作数。
- generate_operator函数生成一个运算符,用于创建数学表达式的操作数。
- generate_expression函数生成数学表达式,可以是整数或分数,并提供一个参数allow_negative,用于控制是否生成负数。
- format_fraction函数将分数格式化为字符串,包括整数、带分数和分数。
- evaluate_expression函数使用eval函数来计算数学表达式的结果,处理可能出现的异常情况,例如零除错误。如果结果是分数,它将进行分数规范化。
- is_valid_expression函数检查数学表达式是否有效,是否可以进行计算。
- generate_problem函数生成一个完整的数学问题,包括多个操作数和运算符,并确保问题是唯一的。
- generate_problems_and_answers函数生成一批数学问题和相应的答案,确保它们是有效的且非负的。
- save_to_file函数将生成的问题和答案保存到文件中。
- validate_and_grade函数用于验证生成的问题和答案是否正确,并生成一个评分报告。
- main函数使用argparse解析命令行参数,并根据参数执行不同的操作。如果提供了验证文件和答案文件,执行验证和评分;如果提供了生成题目参数,生成问题和答案文件;否则提供使用说明。
3. 流程图
代码说明
- 数学问题与答案生成部分代码
点击查看代码
# generate_number 函数生成一个随机整数,用于创建数学表达式的操作数
def generate_number(range_limit):
return random.randint(1, range_limit)
# generate_fraction 函数生成一个随机分数,用于创建数学表达式的操作数
def generate_fraction(range_limit):
numerator = random.randint(1, range_limit)
denominator = random.randint(numerator, range_limit)
return Fraction(numerator, denominator)
# generate_operator 函数生成一个运算符,用于创建数学表达式的操作数。
def generate_operator():
operators = ['+', '-', '*', '/']
return random.choice(operators)
# generate_expression 函数生成数学表达式,并提供一个参数allow_negative,用于控制是否生成负数
def generate_expression(range_limit, allow_negative=False):
if random.random() < 0.5:
return str(generate_number(range_limit))
else:
if allow_negative:
return str(generate_fraction(range_limit))
else:
return str(generate_number(range_limit))
# format_fraction 函数将分数格式化为字符串,包括整数、带分数和分数
def format_fraction(fraction):
if fraction == 0:
return '0'
if fraction.numerator < fraction.denominator:
return f"{fraction.numerator}/{fraction.denominator}"
else:
whole_part = fraction.numerator // fraction.denominator
numerator = fraction.numerator % fraction.denominator
if numerator == 0:
return str(whole_part)
return f"{whole_part}'{numerator}/{fraction.denominator}"
# evaluate_expression 函数使用eval函数来计算数学表达式的结果,处理可能出现的异常情况
def evaluate_expression(expression):
try:
result = eval(expression)
if isinstance(result, Fraction):
return result
return Fraction(result).limit_denominator()
except ZeroDivisionError:
return None
# is_valid_expression 函数检查数学表达式是否有效,是否可以进行计算
def is_valid_expression(expression):
return evaluate_expression(expression) is not None
# generate_problem 函数生成一个完整的数学问题,并确保问题是唯一的
def generate_problem(range_limit, generated_problems):
while True:
num_operators = random.randint(1, 3)
problem = generate_expression(range_limit, allow_negative=True)
for _ in range(num_operators):
operator = generate_operator()
operand = generate_expression(range_limit)
# 如果结果为整数,则将整数形式添加到问题中
if evaluate_expression(problem).denominator == 1:
problem += f" {operator} {int(evaluate_expression(operand))}"
else:
problem += f" {operator} {operand}"
# 检查是否已生成过相同的题目,如果是则重新生成
if problem not in generated_problems:
generated_problems.add(problem)
return problem
# generate_problems_and_answers 函数生成一批数学问题和相应的答案,确保它们是有效的且非负的
def generate_problems_and_answers(num_problems, range_limit, generated_problems):
problems = []
answers = []
while len(problems) < num_problems:
problem = generate_problem(range_limit, generated_problems)
answer = evaluate_expression(problem)
if is_valid_expression(problem) and answer is not None and answer >= 0:
problems.append(problem)
answers.append(answer)
return problems, answers
- 数学问题与答案保存部分代码
点击查看代码
# save_to_file 函数将生成的问题和答案保存到文件中
def save_to_file(problems, answers, problem_file, answer_file):
with open(problem_file, "w") as f:
for i, problem in enumerate(problems, start=1):
f.write(f"{i}. {problem}\n")
with open(answer_file, "w") as f:
for i, answer in enumerate(answers, start=1):
formatted_answer = format_fraction(Fraction(answer))
f.write(f"{i}. {formatted_answer}\n")
- 验证问题与答案部分代码
点击查看代码
# validate_and_grade 函数用于验证生成的问题和答案是否正确,并生成一个评分报告
def validate_and_grade(exercise_file, answer_file):
with open(exercise_file, "r") as f:
problems = [line.strip().split('. ')[1] for line in f if line.strip()]
with open(answer_file, "r") as f:
answers = [line.strip().split('. ')[1] for line in f if line.strip()]
correct = 0
wrong = 0
wrong_indices = []
for i, (problem, answer) in enumerate(zip(problems, answers), start=1):
formatted_answer = format_fraction(evaluate_expression(problem))
if formatted_answer == answer:
correct += 1
else:
wrong += 1
wrong_indices.append(i)
with open("Grade.txt", "w") as f:
f.write(f"Correct: {correct} ({', '.join(map(str, range(1, correct + 1)))})\n")
f.write(f"Wrong: {wrong} ({', '.join(map(str, wrong_indices))})\n")
- 主函数部分代码,主要负责命令行参数解析
点击查看代码
def main():
parser = argparse.ArgumentParser(description="Generate and validate elementary math problems.")
parser.add_argument("-n", type=int, default=None, help="Number of problems to generate")
parser.add_argument("-r", type=int, default=None, help="Range limit for numbers")
parser.add_argument("-e", type=str, help="Exercise file for validation")
parser.add_argument("-a", type=str, help="Answer file for validation")
args = parser.parse_args()
if args.e and args.a:
validate_and_grade(args.e, args.a)
else:
if args.n is not None and args.r is not None:
generated_problems = set() # 用于存储已生成的题目
problems, answers = generate_problems_and_answers(args.n, args.r, generated_problems)
save_to_file(problems, answers, "Exercises.txt", "Answers.txt")
else:
print("usage: Myapp.py -n N -r R")
print("-n and -r are required when generating problems.")
print("usage: Myapp.py -e E -a A")
print("-e and -a are required when checking results.")
测试运行
1.测试generate_number函数是否生成随机整数
def test_generate_number(self):
num = generate_number(10)
self.assertTrue(1 <= num <= 10)
2.测试generate_fraction函数是否生成随机分数
def test_generate_fraction(self):
fraction = generate_fraction(10)
self.assertIsInstance(fraction, Fraction)
self.assertTrue(0 <= fraction <= 1)
self.assertTrue(fraction.numerator <= fraction.denominator <= 10)
3.测试generate_operator函数是否生成运算符
def test_generate_operator(self):
operator = generate_operator()
self.assertIn(operator, ['+', '-', '*', '/'])
4.测试generate_expression函数是否返回一个有效的数学表达式
def test_generate_expression(self):
for _ in range(100):
expression = generate_expression(10, allow_negative=True)
self.assertTrue(is_valid_expression(expression))
5.测试format_fraction函数是否正确格式化分数
def test_format_fraction(self):
fraction = Fraction(3, 4)
formatted = format_fraction(fraction)
self.assertEqual(formatted, '3/4')
6.测试evaluate_expression函数是否正确评估数学表达式
def test_evaluate_expression(self):
expression = '3 + 4'
result = evaluate_expression(expression)
self.assertIsInstance(result, Fraction)
self.assertEqual(result, Fraction(7, 1))
7.测试is_valid_expression函数是否正确检查表达式的有效性
def test_is_valid_expression(self):
self.assertTrue(is_valid_expression("2 + 3"))
self.assertTrue(is_valid_expression("1/2 - 1/3"))
self.assertTrue(is_valid_expression("1 * 0"))
self.assertTrue(is_valid_expression("1 * 0 / 6"))
self.assertFalse(is_valid_expression("1 / 0"))
self.assertFalse(is_valid_expression("1 * 6 / 0"))
self.assertFalse(is_valid_expression("1 / 1/0"))
8.测试generate_problem函数是否生成有效的问题
def test_generate_problem(self):
generated_problems = set()
for _ in range(100):
problem = generate_problem(10, generated_problems)
self.assertTrue(is_valid_expression(problem))
generated_problems.add(problem)
9.测试generate_problems_and_answers函数是否生成有效的问题和答案
def test_generate_problems_and_answers(self):
generated_problems = set()
problems, answers = generate_problems_and_answers(10, 10, generated_problems)
self.assertEqual(len(problems), 10)
self.assertEqual(len(answers), 10)
for problem in problems:
self.assertTrue(is_valid_expression(problem))
for answer in answers:
self.assertIsInstance(answer, Fraction)
10.测试save_to_file函数是否正成功生成数学问题文件与答案文件
def test_save_to_file(self):
# 生成一些虚拟的问题和答案
problems = ["1 + 2", "3 - 1", "4 * 5"]
answers = ["3", "2", "20"]
# 创建一个临时目录来保存测试文件
with tempfile.TemporaryDirectory() as temp_dir:
problem_file = os.path.join(temp_dir, "test_problems.txt")
answer_file = os.path.join(temp_dir, "test_answers.txt")
# 调用被测试的函数
save_to_file(problems, answers, problem_file, answer_file)
# 读取生成的文件内容以进行断言
with open(problem_file, "r") as f:
saved_problems = f.read().splitlines()
with open(answer_file, "r") as f:
saved_answers = f.read().splitlines()
# 断言生成的文件内容是否与输入一致
self.assertEqual(saved_problems, ["1. 1 + 2", "2. 3 - 1", "3. 4 * 5"])
self.assertEqual(saved_answers, ["1. 3", "2. 2", "3. 20"])
11.测试validate_and_grade函数是否正确验证和评分
def test_validate_and_grade(self):
problems = ["1 + 2", "3 - 4"]
answers = [Fraction(3, 1), Fraction(-1, 1)]
save_to_file(problems, answers, "test_problems.txt", "test_answers.txt")
validate_and_grade("test_problems.txt", "test_answers.txt")
with open("Grade.txt", "r") as f:
grade_result = f.readlines()
self.assertTrue("Correct: 2 (1, 2)\n" in grade_result)
self.assertTrue("Wrong: 0 ()\n" in grade_result)
12.测试validate_and_grade函数是否正确验证和评分
def test_validate_and_grade(self):
problems = ["1 + 2", "3 - 4"]
answers = [Fraction(3, 1), Fraction(-1, 1)]
save_to_file(problems, answers, "test_problems.txt", "test_answers.txt")
validate_and_grade("test_problems.txt", "test_answers.txt")
with open("Grade.txt", "r") as f:
grade_result = f.readlines()
self.assertTrue("Correct: 2 (1, 2)\n" in grade_result)
self.assertTrue("Wrong: 0 ()\n" in grade_result)
13.测试main函数是否在输入正确参数的情况下成功运行
@patch('builtins.open', create=True)
def test_main_generate(self, mock_open):
args = argparse.Namespace(n=10, r=10, e=None, a=None)
with patch('argparse.ArgumentParser.parse_args', return_value=args):
main()
# 检查是否生成了 Exercises.txt 和 Answers.txt 文件
mock_open.assert_any_call('Exercises.txt', 'w')
mock_open.assert_any_call('Answers.txt', 'w')
测试结果
项目小结
本次结对编程总体而言是较为成功的。通过这次经历,让我们对结对编程有了更深的理解,包括其中的一些优缺点。
它的优点有许多,包括增加纪律性以及写出更加规范代码等。结对编程时,共同完成一个程序可以避免走进个人误区,且通常个人的想法难免有局限性,双方站在不同的角度能够看到彼此在思考上的不同,从其中选择更为合适的方案对整个程序的开发过程是有极大好处的。当然,代码经过另一个程序员的审查,使得整个程序的设计,编程和测试过程都更加高效,且一定程度上保证了质量。
结对编程也存在一些,例如两个程序员在开发程序过程中很可能就某一问题会产生矛盾与争执,造成不必要的内耗。因此在结对编程过程中及时的沟通协调是非常必要的。有效的沟通与协调能让双方都清楚当下的进度以及目标,在此基础上意见有分歧是正常的情况,但需注意要理性地交流探讨自己的想法。