所属课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
---|---|
作业要求 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230 |
作业目标 | 按照要求合作实现一个自动生成小学四则运算题目的命令行程序 |
成员1 | 于海洋3122004758 |
成员2 | 钟启腾3122004761 |
Github链接: https://github.com/hhai12345678/3122004758
1. PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 300 | 350 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 80 |
· Design Spec | · 生成设计文档 | 50 | 50 |
· Design Review | · 设计复审 | 20 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 60 | 80 |
· Coding | · 具体编码 | 100 | 120 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 80 |
Reporting | 报告 | 60 | 60 |
· Test Repor | · 测试报告 | 30 | 40 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
· 合计 | 890 | 1050 |
2. 效能分析
3. 设计实现过程
3.1 要求分析
-
输入参数:程序需要支持命令行参数 -n、-r、-e 和 -a。其中 -n 表示生成的题目数量,-r 表示数字范围,-e 和 -a 用于验证答案。
-
题目生成:
- 数值范围受 -r 控制,支持自然数和真分数。
- 题目中不能有负数结果,除法结果必须是分数。
- 运算符最多出现 3 次。
-
答案验证:
- 通过对比生成的答案文件与用户提供的答案文件,输出正确与错误的题目编号。
3. 2 实现步骤
-
处理命令行参数。
-
题目生成:
-
随机生成操作数和运算符。
-
检查运算符顺序是否符合规则。
-
检查结果是否合法。
-
-
计算答案。
-
文件输出:
-
题目输出到 Exercises.txt。
-
答案输出到 Answers.txt。
-
-
答案验证:
-
读取用户提供的题目和答案文件。
-
逐一对比,生成统计结果 Grade.txt。
-
3.3 使用方法
生成 10 个数值范围为 10 的题目:
python main.py -n 10 -r 10
验证题目和答案文件:
python main.py -e Exercises.txt -a Answers.txt
4. 代码说明
4.1 代码实现
Main.py代码
import argparse
import random
import fractions
# 定义生成题目所需的常量和函数
OPERATORS = ['+', '-', '*', '/']
def generate_fraction(max_denominator):
denominator = random.randint(2, max_denominator)
numerator = random.randint(1, denominator - 1)
return fractions.Fraction(numerator, denominator)
def generate_operand(max_range):
if random.choice([True, False]):
return str(random.randint(1, max_range - 1)) # 避免出现0作为分母
else:
fraction = generate_fraction(max_range)
return f"{fraction.numerator}/{fraction.denominator}"
def generate_expression(max_range, max_operators):
num_operators = random.randint(1, max_operators)
expression = generate_operand(max_range)
for _ in range(num_operators):
operator = random.choice(OPERATORS)
operand = generate_operand(max_range)
expression += f" {operator} {operand}"
return expression
def calculate_expression(expression):
try:
# 将除法替换为分数形式计算
tokens = expression.split()
result = fractions.Fraction(tokens[0])
for i in range(1, len(tokens), 2):
operator = tokens[i]
operand = fractions.Fraction(tokens[i + 1])
if operator == '+':
result += operand
elif operator == '-':
result -= operand
elif operator == '*':
result *= operand
elif operator == '/':
result /= operand # 保证除法结果为分数
return result
except ZeroDivisionError:
return None
def generate_exercise(num_exercises, max_range, max_operators):
exercises = []
answers = []
while len(exercises) < num_exercises:
expression = generate_expression(max_range, max_operators)
result = calculate_expression(expression)
if result is not None:
exercises.append(expression)
answers.append(
f"{result.numerator}/{result.denominator}" if result.denominator != 1 else str(result.numerator))
return exercises, answers
def write_to_file(filename, lines):
with open(filename, 'w', encoding='utf-8') as file:
for line in lines:
file.write(line + '\n')
def read_file(filename):
with open(filename, 'r', encoding='utf-8') as file:
return [line.strip() for line in file]
def check_answers(exercise_file, answer_file):
exercises = read_file(exercise_file)
answers = read_file(answer_file)
correct_indices = []
wrong_indices = []
for index, (exercise, user_answer) in enumerate(zip(exercises, answers), start=1):
correct_answer = calculate_expression(exercise)
correct_answer_str = f"{correct_answer.numerator}/{correct_answer.denominator}" if correct_answer.denominator != 1 else str(
correct_answer.numerator)
if user_answer == correct_answer_str:
correct_indices.append(index)
else:
wrong_indices.append(index)
return correct_indices, wrong_indices
def write_grades(correct, wrong):
with open('Grade.txt', 'w', encoding='utf-8') as file:
file.write(f"Correct: {len(correct)} ({', '.join(map(str, correct))})\n")
file.write(f"Wrong: {len(wrong)} ({', '.join(map(str, wrong))})\n")
def main():
parser = argparse.ArgumentParser(description="Generate and check elementary arithmetic problems.")
parser.add_argument('-n', type=int, help="Number of exercises to generate.")
parser.add_argument('-r', type=int, help="Range of numbers in exercises.")
parser.add_argument('-e', type=str, help="Exercise file to check answers.")
parser.add_argument('-a', type=str, help="Answer file to check answers.")
args = parser.parse_args()
if args.n and args.r:
exercises, answers = generate_exercise(args.n, args.r, 3)
write_to_file('Exercises.txt', exercises)
write_to_file('Answers.txt', answers)
print(f"Generated {args.n} exercises with answers.")
elif args.e and args.a:
correct, wrong = check_answers(args.e, args.a)
write_grades(correct, wrong)
print("Checked answers and wrote grades to Grade.txt.")
else:
parser.print_help()
if __name__ == "__main__":
main()
测试单元代码
import random
import unittest
import fractions
from io import StringIO
import sys
from main import (generate_fraction, generate_operand, generate_expression,
calculate_expression, generate_exercise, write_to_file, read_file,
check_answers)
class TestMathExercise(unittest.TestCase):
def test_generate_fraction(self):
fraction = generate_fraction(10)
self.assertTrue(isinstance(fraction, fractions.Fraction))
self.assertGreaterEqual(fraction.denominator, 2)
self.assertGreaterEqual(fraction.numerator, 1)
def test_generate_expression(self):
random.seed(0) # 固定随机种子保证生成结果一致
expression = generate_expression(10, 3)
self.assertTrue(any(op in expression for op in ['+', '-', '*', '/']))
def test_calculate_expression_addition(self):
expression = "1/2 + 1/2"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 1))
def test_calculate_expression_subtraction(self):
expression = "3/4 - 1/4"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 2))
def test_calculate_expression_multiplication(self):
expression = "2/3 * 3/4"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(1, 2))
def test_calculate_expression_division(self):
expression = "2/3 / 4/5"
result = calculate_expression(expression)
self.assertEqual(result, fractions.Fraction(10, 12))
def test_generate_exercise(self):
exercises, answers = generate_exercise(5, 10, 3)
self.assertEqual(len(exercises), 5)
self.assertEqual(len(answers), 5)
def test_check_answers(self):
exercises = ["1/2 + 1/2", "3/4 - 1/4", "2/3 * 3/4", "2/3 / 4/5"]
answers = ["1", "1/2", "1/2", "5/6"]
# 模拟写入和读取文件
with open('Exercises.txt', 'w') as f_ex:
f_ex.write('\n'.join(exercises))
with open('Answers.txt', 'w') as f_ans:
f_ans.write('\n'.join(answers))
correct, wrong = check_answers('Exercises.txt', 'Answers.txt')
self.assertEqual(correct, [1, 2, 3, 4])
self.assertEqual(wrong, [])
def test_write_to_file(self):
data = ["1 + 1", "2 - 2", "3 * 3", "4 / 4"]
write_to_file("TestOutput.txt", data)
read_back = read_file("TestOutput.txt")
self.assertEqual(data, read_back)
def test_read_file(self):
data = ["5 + 5", "6 - 6"]
write_to_file("TestRead.txt", data)
read_back = read_file("TestRead.txt")
self.assertEqual(data, read_back)
if __name__ == '__main__':
unittest.main()
4.2 详细说明
-
generate_fraction: 生成随机真分数。
-
generate_operand: 生成随机操作数,可以是自然数或真分数。
-
generate_expression: 根据给定的操作符数量生成一个表达式。
-
calculate_expression: 计算生成的表达式的结果,如果有除以零等非法操作,返回 None。
-
generate_exercise: 根据给定数量和范围生成不重复的题目和答案。
-
write_to_file: 将生成的题目或答案写入文件。
-
read_file: 从文件读取题目或答案。
-
check_answers: 对比题目文件和答案文件,输出正确和错误的编号。
-
write_grades: 将对错题目编号写入文件 Grade.txt。
5. 测试运行
6. 实际运行结果
7. 项目小结
-
在本次项目中,我们加深了对 Python语言理解和掌握。
-
通过设计和实现一个具有特定功能的程序——从需求分析到设计,再到代码实现和测试,我们经历了完整的软件开发流程,积累了宝贵的实践经验。
-
我们学会了共同沟通,共同思考得出解决方法,提高了通过团队解决问题的能力。