结对项目:四则运算自动生成程序
[github地址]
https://github.com/kkrInblU/3222004510.git
https://github.com/115any/3222004512.git
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/ |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230 |
我的搭档 | 杨殷 |
这个作业的目标 | 结对开发程序,实现共同合作 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 20 |
Estimate | 估计这个任务需要多少时间 | 900 | 1000 |
Development | 开发 | 420 | 600 |
Analysis | 需求分析 (包括学习新技术) | 30 | 25 |
Design Spec | 生成设计文档 | 20 | 20 |
Design Review | 设计复审 | 20 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 35 |
Design | 具体设计 | 50 | 40 |
Coding | 具体编码 | 50 | 70 |
Code Review | 代码复审 | 20 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 40 | 60 |
Reporting | 报告 | 20 | 30 |
Test Repor | 测试报告 | 40 | 60 |
Size Measurement | 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 780 | 1020 |
二、效能分析
记录你在改进程序性能上花费了多少时间,描述你改进的思路,并展示一张性能分析的图。如果可能,展示你程序中消耗最大的函数。(3分)
三、设计实现过程
功能函数
format_fraction
:格式化分数。is_integer
:判断字符串是否为整数。generate_fraction
:生成真分数。generate_number
:生成自然数或真分数。generate_expression
:递归生成四则运算表达式。generate_question
:生成一个完整的四则运算题目。evaluate_expression
:计算表达式的值。generate_and_save_questions
:生成题目和答案并保存到文件。check_answers
:检查答案的正确性并统计正确和错误的数量。
实施流程图
flowchart TD
Start(开始) --> Init{初始化随机数生成器}
Init --> ParseArgs[解析命令行参数]
ParseArgs --> CheckArgs{检查参数}
CheckArgs -- 是 --> GenSaveQ[generate_and_save_questions]
CheckArgs -- 否 --> CheckAns[check_answers]
GenSaveQ --> GenExpr[generate_expression]
GenExpr --> GenNum[generate_number]
GenNum --> GenFrac[generate_fraction]
GenExpr --> GenQ[generate_question]
GenQ --> EvalExpr[evaluate_expression]
EvalExpr --> FormatFrac[format_fraction]
CheckAns --> OpenExer[打开练习文件]
CheckAns --> OpenAns[打开答案文件]
OpenExer --> ReadExer[读取所有练习题目]
OpenAns --> ReadAns[读取用户答案]
ReadExer --> CalcExpr[计算表达式值]
CalcExpr --> EvalExpr
ReadAns --> CompareAns[比较标准答案和用户答案]
CompareAns -->|答案正确| RecordCorrect[记录正确题目索引]
CompareAns -->|答案错误| RecordWrong[记录错误题目索引]
RecordCorrect --> SaveGrade[保存成绩报告]
RecordWrong --> SaveGrade
SaveGrade --> CloseFiles[关闭文件]
CloseFiles --> End(结束)
classDef startend fill:#f96,stroke:#333,stroke-width:2px;
classDef function fill:#9f6,stroke:#333,stroke-width:1px;
classDef decision fill:#f96,stroke:#333,stroke-width:1px;
class Start,End startend;
class ParseArgs,CheckArgs,GenSaveQ,CheckAns,GenExpr,GenNum,GenFrac,GenQ,EvalExpr,FormatFrac,OpenExer,OpenAns,ReadExer,ReadAns,CalcExpr,CompareAns,RecordCorrect,RecordWrong,SaveGrade,CloseFiles function;
函数之间的关系
generate_number
调用generate_fraction
来生成分数。generate_expression
调用generate_number
来生成表达式中的数字或分数。generate_question
调用generate_expression
来生成题目。evaluate_expression
调用format_fraction
来格式化计算结果。generate_and_save_questions
调用generate_question
和evaluate_expression
来生成题目和答案。check_answers
调用evaluate_expression
来计算标准答案并与用户答案比较。
四、代码说明
生成问题
实施流程
flowchart TD
Start(开始生成问题) --> Init{初始化}
Init -->|初始化问题和答案列表| GenQA[generate_and_save_questions]
GenQA --> GenExpr[generate_question]
GenExpr --> Eval[evaluate_expression]
Eval --> Check{答案是否有效}
Check -- 是 --> Format[format_fraction]
Check -- 否 --> GenExpr
Format --> Save{保存问题和答案}
Save -->|问题数量达到要求| EndGenQA[结束生成问题]
Save --> GenExpr
EndGenQA --> WriteQ[写入问题到文件]
EndGenQA --> WriteA[写入答案到文件]
WriteQ --> End(结束)
classDef startend fill:#f96,stroke:#333,stroke-width:2px;
classDef function fill:#9f6,stroke:#333,stroke-width:1px;
classDef decision fill:#f96,stroke:#333,stroke-width:1px;
class Start,End startend;
class GenQA,Eval,Check,Format,Save function;
# 生成指定数量的问题和答案,并保存到文件
def generate_and_save_questions(num_questions, range_num, exercise_filename, answer_filename):
questions = [] # 存储问题
answers = [] # 存储答案
while len(questions) < num_questions:
question = generate_question(range_num) # 生成一个问题
answer = evaluate_expression(question[:-1]) # 计算答案
if answer != "Error" and all(question not in q for q in questions):
questions.append(question) # 如果答案正确且问题不重复,添加到列表
answers.append(answer)
with open(exercise_filename, 'w') as ef: # 打开练习文件
for item in questions:
ef.write(item + '\n') # 写入问题
with open(answer_filename, 'w') as af: # 打开答案文件
for item in answers:
af.write(item + '\n') # 写入答案
计算算式的答案
实施流程
flowchart TD
Start(开始计算答案) --> Replace[替换错误的单引号]
Replace --> Split[分割表达式]
Split --> Eval[计算表达式的值]
Eval --> Format[format_fraction]
Format --> End(结束)
classDef startend fill:#f96,stroke:#333,stroke-width:2px;
classDef function fill:#9f6,stroke:#333,stroke-width:1px;
class Start,End startend;
class Replace,Split,Eval,Format function;
# 计算表达式的值,将带分数转换为加法形式后用 eval 函数求值
def evaluate_expression(expr):
expr = expr.replace("'", '+') # 替换错误的单引号为加号
try:
parts = expr.split(' ') # 分割表达式
new_expr = ''
for part in parts:
if '/' in part:
fraction = Fraction(part) # 如果部分是分数,转换为Fraction对象
new_expr += f'{fraction} ' # 添加到新的表达式字符串
else:
new_expr += f'{part} ' # 否则直接添加
result = eval(new_expr) # 计算表达式的值
return format_fraction(Fraction(result)) # 返回格式化后的分数
except:
return "Error" # 如果计算出错,返回"Error"
检查答案正确性
实施流程
flowchart TD
Start(开始检查答案) --> OpenFiles[打开练习和答案文件]
OpenFiles --> ReadExer[读取练习题目]
OpenFiles --> ReadAns[读取用户答案]
ReadExer --> CalcAns[calculated_answer]
ReadAns --> GetAns[获取用户答案]
GetAns --> Compare{比较答案}
Compare -- 相同 --> Correct[记录正确答案]
Compare -- 不同 --> Wrong[记录错误答案]
Correct --> SaveGrade[保存成绩报告]
Wrong --> SaveGrade
SaveGrade --> CloseFiles[关闭文件]
CloseFiles --> End(结束)
classDef startend fill:#f96,stroke:#333,stroke-width:2px;
classDef function fill:#9f6,stroke:#333,stroke-width:1px;
class Start,End startend;
class OpenFiles,ReadExer,ReadAns,CalcAns,GetAns,Compare,Correct,Wrong,SaveGrade,CloseFiles function;
# 检查答案的正确性并统计正确和错误的数量,输出到 Grade.txt 文件
def check_answers(exercise_file, answer_file):
correct_count = 0 # 正确数量
wrong_count = 0 # 错误数量
correct_indices = [] # 正确题目的索引
wrong_indices = [] # 错误题目的索引
answer_f = open(answer_file, 'r') # 打开答案文件
with open(exercise_file, 'r') as ef: # 打开练习文件
exercises = ef.readlines() # 读取所有练习题目
for i, exercise in enumerate(exercises, start=1):
exercise_expr = exercise[:-1].split('=')[0] # 获取表达式
calculated_answer = evaluate_expression(exercise_expr) # 计算答案
answer_line = answer_f.readline().strip() # 获取用户答案
# 将答案转换为统一格式进行比较
try:
if calculated_answer != "Error":
formatted_calculated_answer = Fraction(calculated_answer) # 格式化计算答案
else:
formatted_calculated_answer = calculated_answer
if answer_line != "Error":
formatted_answer_line = Fraction(answer_line) # 格式化用户答案
else:
formatted_answer_line = answer_line
if formatted_calculated_answer == formatted_answer_line:
correct_count += 1 # 如果答案相同,增加正确数量
correct_indices.append(i) # 添加正确题目索引
else:
wrong_count += 1 # 如果答案不同,增加错误数量
wrong_indices.append(i) # 添加错误题目索引
except ValueError:
wrong_count += 1 # 如果转换出错,增加错误数量
wrong_indices.append(i) # 添加错误题目索引
answer_f.close() # 关闭答案文件
with open('Grade.txt', 'w') as gf: # 打开成绩文件
gf.write(f'Correct: {correct_count} ({", ".join(map(str, correct_indices))})\n') # 写入正确信息
gf.write(f'Wrong: {wrong_count} ({", ".join(map(str, wrong_indices))})\n') # 写入错误信息
五、测试运行
-
测试用例 1:生成单个问题
- 输入:
num_questions=1, range_num=10
- 预期:生成一个包含随机四则运算的题目。
- 输入:
-
测试用例 2:生成多个问题
- 输入:
num_questions=5, range_num=5
- 预期:生成五个不重复的四则运算题目。
- 输入:
-
测试用例 3:包含所有运算符
- 输入:
num_questions=10, range_num=2
- 预期:生成的题目中包含加、减、乘、除的所有运算符。
- 输入:
-
测试用例 4:检查答案功能
- 输入: python main.py -e Exercises.txt -a Answers.txt
- 预期:正确找出错误答案并给出题号
-
测试用例 5:数值范围为0(无效范围)
- 输入: python main.py -n 10 -r 0
- 预期:报错。
-
测试用例 6:题目数量为0
- 输入: python main.py -n 0 -r 10
- 预期:报错。
-
测试用例 7:题目数量为1.99(无效数量)
- 输入:python main.py -n 1.99 -r 10
- 预期:报错
-
测试用例 8:必要参数缺失
- 输入:python main.py -n 10
- 预期:程报错
-
测试用例 9:大量、大范围算式生成(性能测试)
- 输入:用户答案文件包含错误答案。
- 预期:程序正确标记错误答案并给出错误题目的索引。
-
测试用例 10:性能测试
- 输入:python main.py -n 1000 -r 1000
- 预期:程序在生成大量题目时仍能保持合理的响应时间。
六、项目小结
在我们的结对编程项目中,我们通过有效沟通、实时代码审查和相互学习,成功实现了这个项目。我们面临的挑战包括工作节奏的差异、解决冲突和时间管理,即使课程安排一直,但个人时间的分配很难统一,在多次的沟通和协商之下才能确定分工、需求分析和设计以及实施等。我们学到了准备的重要性、适应性的必要和持续反馈的价值。结对编程不仅增强了我们的技术能力,也加深了我们的友谊。我们互相赞赏对方的耐心、创新思维和对细节的关注,并建议未来项目中更多地使用自动化测试和代码重构。这次经历让我们坚信结对编程是一种宝贵的实践。