结对编程项目
| 这个作业属于哪个课程 | 软件工程 |
| ---- | ---- | ---- |
| 这个作业要求在哪里 | 结对编程 |
|这个作业的目标|实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)|
|成员|王楷楠(3118005425) 钟文磊(3118005435)|
github地址
项目地址
核心算法 原生 node 模块
用户界面
用户界面 Release
PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟)|
| ---- | ---- | ---- |----|----|
| Planning | 计划 | 30 | 30 |
|Estimate| 估计这个任务需要多少时间| 10| 5 |
|Development | 开发| 600|800 |
|Analysis|需求分析 (包括学习新技术)|180|200|
|Design Spec|生成设计文档|20|15|
|Design Review|设计复审|15|15|
| Coding Standard|代码规范 (为目前的开发制定合适的规范)|10|10|
|Design|具体设计|60|80|
|Coding|具体编码|120|150|
|Code Review|代码复审|60|100|
|Test|测试(自我测试,修改代码,提交修改)|120|150|
|Reporting|报告|30|120|
|Test Repor|测试报告|30|120|
|Size Measuremen|计算工作量|5|5|
|Postmortem & Process Improvement Plan| 事后总结, 并提出过程改进计划|30|30|
||合计|1285|1830|
简介
本项目使用 C++ 完成核心算法,使用 Electron 及 Vuejs 3 构建用户界面,并通过 Node.js 提供的 N-API 和 node-gyp 将 C++ 算法编译成能被用户界面使用的 .node
二进制文件。
核心流程图与设计思想:
效能分析
1.cpu占比
2.内存占比
3.综合分析
单元测试
在原CPP文件中进行单元测试
1.测试用例
-n | -r | |
---|---|---|
1 | 100 | 10 |
2 | 10000 | 100 |
2.测试结果
编译成原生 node 模块后使用 mocha
进行单元测试
- 单元测试代码
const assert = require('assert');
const { generate } = require('bindings')('arithmetic-expression-generator');
let randTotal = Math.ceil(Math.random() * 900 + 10);
describe('# test generator', () => {
try {
res = generate(randTotal, 1000);
for (let i = 1; i <= randTotal; i++) {
let { expression, answer } = res[i - 1];
let formattedExpression = expression
.replace(/÷/g, '/')
.replace(/×/g, '*');
let expressionEval = eval(formattedExpression).toFixed(4),
answerEval = eval(answer).toFixed(4);
it(`Round ${i}: ${expression} should equal to ${answer}`, () => {
assert.strictEqual(expressionEval, answerEval);
});
}
} catch (err) {
console.log(err);
}
});
- 运行结果
部分函数
void solve(int k)
1.参数:生成自然数的范围
2.功能:判断生成分式或整式
bool solve1(bool flag,int k)
1.参数:括号生成标记,生成自然数的范围
2.功能:随机生成操作数,运算符和括号的个数,生成中缀表达式
bool solve2()
1.功能:生成分式中缀表达式
bool getOperands(int k)
1.参数:生成自然数的范围
2.功能:将中缀表达式转换成后缀表达式(也就是逆波兰表达式),对操作数随机赋值
bool GetOperands(int32_t k)
{
GetOperandsInit();
// 将中缀表达式转换成后缀表达式(也就是逆波兰表达式)
if (hasBracket[0] != -1)
opr.push(hasBracket[0]);
RPN[tot++] = 0;
for (int32_t i = 1; i < operandNum; ++i)
{
while (true)
{
if (opr.empty() || opr.top() == 14 || pri[operators[i - 1]] > pri[opr.top()])
{
opr.push(operators[i - 1]);
break;
}
RPN[tot++] = opr.top();
opr.pop();
}
if (hasBracket[i] == 14)
{
opr.push(hasBracket[i]);
}
RPN[tot++] = i;
if (hasBracket[i] == 15)
{
while (opr.top() != 14)
{
RPN[tot++] = opr.top();
opr.pop();
}
opr.pop();
}
}
while (!opr.empty())
{
RPN[tot++] = opr.top();
opr.pop();
}
// 转换成逆波兰表达式后便可以进行尝试填数
for (int32_t i = 0; i < tot; ++i)
{
// 如果为运算数则随机为其赋值
if (RPN[i] < 10)
{
int32_t x = GetNum(1, k);
operands[RPN[i]] = x;
opd.push(node(RPN[i], x));
continue;
}
//如果为除法,要将除数随机分配为被除数的一个因子
//如果为减法,要注意减数不能大于被减数
if (RPN[i] == 13)
{
node b = opd.top();
opd.pop();
node a = opd.top();
opd.pop();
if (a.val % b.val == 0)
{
opd.push(node(-1, a.val / b.val));
continue;
}
if (b.id == -1)
return false;
int32_t cnt = 0;
for (int32_t j = 1; j <= a.val; ++j)
{
if (j >= 100)
break;
if (a.val % j)
continue;
fac[cnt++] = j;
}
int32_t x = GetNum(0, cnt - 1);
operands[b.id] = fac[x];
opd.push(node(-1, a.val / fac[x]));
}
else if (RPN[i] == 11)
{
node b = opd.top();
opd.pop();
node a = opd.top();
opd.pop();
int32_t dt = a.val - b.val;
if (dt <= 0)
return false;
opd.push(node(-1, dt));
}
else
{
node b = opd.top();
opd.pop();
node a = opd.top();
opd.pop();
if (RPN[i] == 10)
opd.push(node(-1, a.val + b.val));
if (RPN[i] == 12)
opd.push(node(-1, a.val * b.val));
}
}
ans = opd.top().val;
opd.pop();
//控制最终运算结果的范围,可根据需要进行调节
if (ans < 0 || ans > 1000)
return false;
return true;
}
程序运行截图
- 主界面(支持浅色/深色模式,随系统设置切换)
- 生成结果并导出文件到程序目录
- 文件截图
项目小结:
钟文磊:这次项目时间较长,准备时间相较上一次较为充裕,结合所学的本以为是挺好完成的项目,直到准备开发的时候才发现看似算法简单,实则操作起来很复杂,要考虑的方面很多,特别是数值较大的时候算法的不严谨性就暴露的很明显,经过一次次和好兄弟的修改,最终完成了这次项目,一起互相学习的过程受益无穷。
王楷楠:在这次项目中,比较麻烦的应该是把文磊写好的 C++ 程序改成 N-API 要求的格式并编译成二进制文件,以及一些 Webpack 的配置。经过多种渠道的搜寻,最终还是完成了项目。