结对编程项目

| 这个作业属于哪个课程 | 软件工程 |
| ---- | ---- | ---- |
| 这个作业要求在哪里 | 结对编程 |
|这个作业的目标|实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)|
|成员|王楷楠(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 进行单元测试

  1. 单元测试代码
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);
    }
});
  1. 运行结果

部分函数

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 的配置。经过多种渠道的搜寻,最终还是完成了项目。

posted @ 2020-10-13 00:47  sidzwl  阅读(182)  评论(0编辑  收藏  举报