前言:这个项目是结对编程的任务。我们基于JavaScript在Node.js环境下实现了基于命令行的四则运算器。
项目详述
在这个项目中,我们需要实现一个命令行程序,以便为小学自动生成四个算术问题。
小组成员:吴堂煌,张国峻
GitHub: https://github.com/m8705/Arithmometer
详细说明
程序使用
生成问题:
node e.js -n 10 -r 100
验证练习:
node e.js -e exercisefile.txt -a answerfile.txt
参数和规定
- 使用-n参数控制生成的问题数(1~10000)。
- 使用-r参数控制题目中的数值范围(自然数,分数分子和分母)(1~100)。
- 生成的问题中的计算过程不会产生负数,也就是说,如果算术表达式中有子表达式,例如e1-e2,那么e1>e2
- 如果生成的练习中存在子表达式e1÷e2,则结果应为分数。
- 每个问题中不超过3个运算符。
- 一次运行的程序产生的问题不能重复,也就是说,任何两个问题都不能通过有限数量的交换+和*算术表达式转换成同一个问题。生成的问题存储在练习中。 txt文件位于执行程序的当前目录下。
- 同时,计算所有问题的答案并将其存储在执行程序的当前目录中的Answers.txt文件中。
- 该计划应该支持产生一万个问题。
- 程序支持给定的问题文件和答案文件,确定正确和错误的答案和统计,统计输出到文件Grade.txt
主要代码
分数转换
function convert(str){//将任何数转成真分数(小数不换)
//整数 2 = 2'1/1
//真分数 3/8
//假分数 5/3
//带分数 1'1/2
//console.log(str)
if( str.indexOf("/") >= 0 ){//真分数或带分数
if( str.indexOf("'") >= 0 ){//带分数
first = str.split("'")[0];
second = str.split("'")[1];
up = second.split("/")[0];
down = second.split("/")[1];
if( ( up === down ) || ( down === "1" ) ){//带分数情况下,不可能存在分子分母相同或分母为1的情况
return "ERROR";
}
str = ( (+first) * (+down) + (+up) ) + "/" + down;
}
else{//真分数
;
}
}
else{//整数
str = str + "/1";
}
return str
//console.log(str);
}
分数运算
function calculate(num1,num2,operator){//根据数字和符号进行分数运算
var n1 = [];
var n2 = [];
var result;
n1 = convert(num1).split( "/" ); // [ 0分子,1分母 ]
n2 = convert(num2).split( "/" ); // [ 0分子,1分母 ]
switch(operator){
case "+":
result = (n1[0]*n2[1]+n2[0]*n1[1]) + "/" + (n1[1]*n2[1]);
break;
case "-":
result = (n1[0]*n2[1]-n2[0]*n1[1]) + "/" + (n1[1]*n2[1]);
break;
case "*":
result = (n1[0]*n2[0]) + "/" + (n1[1]*n2[1]);
break;
case "/":
result = (n1[0]*n2[1]) + "/" + (n1[1]*n2[0]);
break;
}
//console.log(result);
return result;
}
符号生成
function produceSymbol(){//产生符号
var symbol = Math.random();
var symbolNum;
if( symbol <= 1/3 ){//生成一个符号
symbolNum = 1;
}
else if( symbol <= 2/3 ){//生成两个符号
symbolNum = 2;
}
else{//生成三个符号
symbolNum = 3;
}
var symbolChoice = [];
var tmp;
for(var a = 0; a < symbolNum; a++){//用概率决定符号
tmp = Math.random();
if( tmp <= 1/4 ){
symbolChoice.push("+");
}
else if( tmp <= 2/4 ){
symbolChoice.push("-");
}
else if( tmp <= 3/4 ){
symbolChoice.push("*");
}
else{
symbolChoice.push("/");
}
}
return symbolChoice;
}
数字生成
function produceNumber(symbolNum, range){//产生数字
var symbolChoice = produceSymbol();
var numType;
var numChoice = [];
var up, down;
for( var b = 0; b < symbolNum + 1; b++ ){//用概率决定数字
numType = Math.random();
if( numType <= 7 / 10 ){//生成整数
numChoice.push( Math.floor(Math.random()*range) + "" );
}
else{//生成分数或1(避免生成分子或分母为0)
up = Math.ceil( Math.random() * range );//向上取整
down = Math.ceil( Math.random() * range );//向上取整
if( up === down ){//分子分母相同
numChoice.push("1");
continue;
}
var tmp = Math.random();//是否产生带分数
if( tmp <= 1/4 ){//产生带分数
while(up <= down || (up%down === 0) ){//重新产生带分数
up = Math.ceil( Math.random() * range );//向上取整
down = Math.ceil( Math.random() * range );//向上取整
}
numChoice.push(
(up - up%down)/down +
"'" +
(up%down / gcd(up%down,down)) +
"/" +
down / gcd(up%down,down)
);
}
else{//产生分数
numChoice.push( up + "/" + down );
}
}
}
return numChoice;
}
生成数组
function produceRightArray(n, range){//产生n组符合规定的数字和符号
var rightArray = [];
var flag;
for(var a = 0; a < n; a++){//循环n次
flag = "";
symbolChoice = produceSymbol();
numChoice = produceNumber(symbolChoice.length,range);
for(var b = 0; b < symbolChoice.length; b++ ){//遍历检查每个符号
if( symbolChoice[b] === "*" || symbolChoice[b] === "/" ){
if(numChoice[b] === "0" || numChoice[b+1] === "0"){
flag = "err";
a--;
break;
}
}
}
//console.log(a + flag);
if(flag !== "err"){
rightArray.push([
symbolChoice,numChoice
]);
}
}
//console.log(rightArray);
return rightArray;
}
生成题目
function produceExercise(n,range){//产生n个习题(题目+答案)
var expression = [];
var tmp = "";//保存用于产生结果的算式
var tmp1 = "";//保存用于产生题目的算式
var rightArray = produceRightArray(n,range);
for(var a = 0; a < n; a++ ){//遍历每个产生的结果数组,分别验算结果是否非负
tmp = "";
tmp1 = ""
tmp += "(" + convert(rightArray[a][1][0]) + ")" ;
tmp1 += rightArray[a][1][0];
for(var b = 0; b < rightArray[a][0].length; b++ ){//符号+数字
tmp += rightArray[a][0][b] + "(" + convert(rightArray[a][1][b+1]) + ")";
tmp1 += " " + rightArray[a][0][b] + " " + rightArray[a][1][b+1];
}
while( eval(tmp) < 0 ){//不允许产生算式最终值小于0的情况
rightArray[a] = produceRightArray(1,range)[0];
tmp = "";
tmp1 = "";
tmp += convert(rightArray[a][1][0]);
tmp1 += rightArray[a][1][0];
for(var c = 0; c < rightArray[a][0].length; c++ ){//符号+数字
tmp += rightArray[a][0][c] + "(" + convert(rightArray[a][1][c+1]) + ")";
tmp1 += " " + rightArray[a][0][c] + " " + rightArray[a][1][c+1];
}
}
//console.log(tmp);
expression.push(tmp1);
}
//console.log(expression)
//console.log(rightArray);
//遍历符号列表,根据优先级(先乘除,后加减)对数进行运算,并更新运算结果(逐一替换)至数组
var tmpArray = rightArray;
var operator;
var symIndex;
var numIndex1, numIndex2;
var answer = [];
for(var d = 0; d < n; d++){
for(var e = 0; e < tmpArray[d][0].length; e++){//先进行乘除运算
operator = tmpArray[d][0][e];
switch(operator){
case "*":
//console.log(tmpArray[d][1][e],tmpArray[d][1][e+1]);
replaceNumber(tmpArray[d][1],tmpArray[d][1][e],calculate( convert(tmpArray[d][1][e]),convert(tmpArray[d][1][e+1]),operator ) );
removeOperator(tmpArray[d][0],"*");
e--;
break;
case "/":
//console.log(tmpArray[d][1][e],tmpArray[d][1][e+1]);
replaceNumber(tmpArray[d][1],tmpArray[d][1][e],calculate( convert(tmpArray[d][1][e]),convert(tmpArray[d][1][e+1]),operator ) );
removeOperator(tmpArray[d][0],"/");
e--;
break;
}
}
//console.log(tmpArray)
for(var f = 0; f < tmpArray[d][0].length; f++){//后进行加减运算
operator = tmpArray[d][0][f];
switch(operator){
case "+":
//console.log(tmpArray[d][1][f],tmpArray[d][1][f+1]);
replaceNumber(tmpArray[d][1],tmpArray[d][1][f],calculate( convert(tmpArray[d][1][f]),convert(tmpArray[d][1][f+1]),operator ) );
removeOperator(tmpArray[d][0],"+");
f--;
break;
case "-":
//console.log(tmpArray[d][1][f],tmpArray[d][1][f+1]);
replaceNumber(tmpArray[d][1],tmpArray[d][1][f],calculate( convert(tmpArray[d][1][f]),convert(tmpArray[d][1][f+1]),operator ) );
removeOperator(tmpArray[d][0],"-");
f--;
break;
}
}
answer.push( simplify(tmpArray[d][1][0]) );
}
//console.log(answer);
return [expression,answer];
}
生成答案
function produce(n, range){//产生结果
var exercise = produceExercise(n, range);
var expression = exercise[0];
var answer = exercise[1];
var expressionText = "";
var answerText = "";
for( var a = 0 ; a < n ; a++ ){
expressionText += (a+1) + ". " + expression[a] + "\r\n";
answerText += (a+1) + ". " + answer[a] + "\r\n";
}
//console.log(expressionText)
var fs = require("fs");
fs.writeFile('Exercises.txt', expressionText, function(err) {
if (err) {
return console.error(err);
}
});
//console.log(answerText);
fs.writeFile('Answers.txt', answerText, function(err) {
if (err) {
return console.error(err);
}
});
console.log("题目数据写入成功!请查看 Exercises.txt ");
console.log("答案数据写入成功!请查看 Answers.txt ");
console.log("--------我是分割线-------------")
}
PSP2.1 | 预计耗时 | 实际耗时 |
---|---|---|
总体计划 | 120 | 60 |
预计完成 | 15 | 15 |
程序开发 | 360 | 300 |
需求分析 | 60 | 60 |
设计文档 | 30 | 30 |
设计复审 | 30 | 30 |
代码规范 | 15 | 15 |
具体设计 | 60 | 60 |
具体编码 | 120 | 120 |
代码复审 | 120 | 120 |
程序测试 | 40 | 40 |
程序报告 | 90 | 90 |
测试报告 | 60 | 60 |
计算工作量 | 30 | 30 |
事后总结 | 30 | 30 |
总计时间 | 1180 | 1180 |
测试
生成
扫描二维码关注公众号,回复:
3410165 查看本文章
检查
总结
这次由于我们日常所使用的语言不同,所以我们在开发过程中采用了先研究算法再编程实现的流程,最后我发现,先规划好核心算法之后再进行编程实现,比直接进行开发会更加便于优化和检查错误。