【软工结对项目】四则运算生成
姓名:程孟祺
队友:邵子涵、韩昌云
GitHub地址
https://github.com/Duuang/fast-calculation
项目描述
写一个能自动生成小学四则运算计算题目的命令行软件,要实现以下功能:
- 一次可以生成一千道题目,并且没有重复的,写入文件中。
- 实现包含多个运算符(加减乘除、左右括号)的运算,最多十个运算符。
- 支持真分数和 整数的混合运算。
- 让程序接收用户输入答案,并判断对错,给出对和错的数量。
- 让程序支持乘方运算,用 ‘^’ 或 ‘**’ 表示乘方运算符。
- 把程序变成一个Windows电脑图形界面的程序。
时间计划
面向对象分析
在静态分析中,我们决定采用Question类来存储题目信息。同时在Question类中建立两个嵌套类QuestionGenerator、QuestionCalculator。通过QuestionGenerator类来实现题目生成,通过QuestionCalculator实现题目求解。
另外,我们设计了Fraction类,用于存放真分数,及真分数计算和化简方法。整数也可以存放在Fraction类中。
在这个项目中,我主要负责QuestionCalculator的设计和实现、项目的功能分析等。
核心代码
由于我主要负责QuestionCalculator类,因此首先介绍题目计算部分。在题目计算中,通过Question类中的Calculate方法,调用其嵌套类QuestionCalculator类中的Calculate方法。然后将去除的中缀表达式表示的题目转化为计算机可以理解的后缀表达式。接着调用私有方法lev、cal求解。
下面是QuestionCalculator中的各相关方法的核心代码:
Calculate
//根据后缀表达式求解
stack<Fraction> opestack; //定义操作数栈,<>里内容待定
while (pos_ques[i] != '\0')
{
if (pos_ques[i] == ' ')
{
i++;
continue;
}
if (pos_ques[i] >= '0' && pos_ques[i] <= '9')
{
Fraction fra;
int numerator, denominator;
numerator = atoi(&pos_ques[i]);
while (pos_ques[i] >= '0' && pos_ques[i] <= '9') i++;
if (pos_ques[i] == '/')
{
i += 1;
denominator = atoi(&pos_ques[i]);
while (pos_ques[i] >= '0' && pos_ques[i] <= '9') i++;
Fraction f(numerator, denominator);
fra = f;
}
else {
Fraction f(numerator, 1);
fra = f;
}
opestack.push(fra);
}
else {
Fraction o1, o2;
o2 = opestack.top();
opestack.pop();
o1 = opestack.top();
opestack.pop();
Fraction o3;
o3 = cal(o1, o2, pos_ques[i]);
opestack.push(o3);
i++;
}
}
Fraction value = opestack.top();
opestack.pop();
//化简结果
Fraction sim_value(value.Simplify());
中缀转后缀(PostfixExpressionGenerate)
while (ques[i] != '\0')
{
if (ques[i] == ' ');
if (ques[i] >= '0' && ques[i] <= '9')
{
while (ques[i] >= '0' && ques[i] <= '9')
{
pos_ques = pos_ques + ques[i];
i++;
}
if (ques[i] == ' ' || ques[i] == '\0' || ques[i] == ')')
{
pos_ques = pos_ques + ' ';
if (ques[i] == '\0')
break;
}
if(ques[i] == '/')
{
pos_ques = pos_ques + ques[i];
}
}
if(ques[i] == '(' || ques[i] == ')' || ques[i] == '^' || ques[i] == '+' || ques[i] == '-' || ques[i] == '*' || (ques[i] == '/' && (ques[i + 1] < '0' || ques[i + 1] > '9')) )
{
if (symstack.empty())
symstack.push(ques[i]);
else {
if (ques[i] == '(')
{
symstack.push(ques[i]);
}
else {
if (ques[i] == ')')
{
while (symstack.top() != '(')
{
pos_ques = pos_ques + symstack.top() + ' ';
symstack.pop();
}
symstack.pop();
}
else {
int l1, l2;
l1 = lev(ques[i]);
l2 = lev(symstack.top());
while (l1 <= l2)
{
pos_ques = pos_ques + symstack.top() + ' ';
symstack.pop();
if (symstack.empty()) break;
else l2 = lev(symstack.top());
}
symstack.push(ques[i]);
}
}
}
}
i++;
}
while (!symstack.empty())
{
pos_ques = pos_ques + symstack.top() + ' ';
symstack.pop();
}
return pos_ques;
单元测试
在对题目计算模块的测试中,我采用了课上学过的路径覆盖法。由于题目中有太多循环和分支,电脑作图不方便,因此流图不在此处给出。
通过阅读代码,手绘流图,得出,中缀转后缀模块共有七条线性独立路径,对应的测试用例为:
Test1: 空字符串 Result: 不符合要求,无用例
Test2: 1 Result: 1
Test3: 1 + 2 Result: 1 2 +
Test4: 1 + (1 + 2) Result: 1 1 2 + +
Test5: 1 + (1 + 2) Result: 1 1 2 + +
Test6: 1 + 2 * 3 Result: 1 2 3 * +
Test7: 1 * 2 + 3 Result: 1 2 * 3 +
同理,后缀表达式计算模块共有三条线性独立路径以下是我采用的测试用例:
Test1: 1 + (2 + 3) Result: 6
Test2: 2 * (3 * 4 * (1 + 1)) Result: 48
Test3: (13 / 20) + 8 * 2 / 24 - 2/3 + 2 ** 2 Result: 93/20
性能优化
性能优化过程中,我们控制了生成表达式的规模和生成随机数的规模。优化了写入文件模块,减少了调用次数。在在生成后缀表达式模块中加入了跳出条件,达到条件自动跳出循环,提高了效率。
实际时间开销
补充
GUI及题目生成见队友博客
GUI及软件主体框架等:https://blog.csdn.net/qq_37571192/article/details/86619482
题目生成:
https://blog.csdn.net/weixin_44072868/article/details/86619012
小结
通过这次项目,我对软件开发流程有了更深入的理解。这几乎是我第一次团队协作、严格按照软件开发流程的一次软件项目开发。通过这次项目,我对git的分支控制、代码格式的规范、软件测试相关方法的掌握有了较大的提高,受益匪浅。这些经验,对我今后的软件开发和学习,有很大帮助。