四则预算:Node.js实现

前言:这个项目是结对编程的任务。我们基于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

参数和规定

  1. 使用-n参数控制生成的问题数(1~10000)。
  2. 使用-r参数控制题目中的数值范围(自然数,分数分子和分母)(1~100)。
  3. 生成的问题中的计算过程不会产生负数,也就是说,如果算术表达式中有子表达式,例如e1-e2,那么e1>e2
  4. 如果生成的练习中存在子表达式e1÷e2,则结果应为分数。
  5. 每个问题中不超过3个运算符。
  6. 一次运行的程序产生的问题不能重复,也就是说,任何两个问题都不能通过有限数量的交换+和*算术表达式转换成同一个问题。生成的问题存储在练习中。 txt文件位于执行程序的当前目录下。
  7. 同时,计算所有问题的答案并将其存储在执行程序的当前目录中的Answers.txt文件中。
  8. 该计划应该支持产生一万个问题。
  9. 程序支持给定的问题文件和答案文件,确定正确和错误的答案和统计,统计输出到文件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

测试

生成

检查

总结

这次由于我们日常所使用的语言不同,所以我们在开发过程中采用了先研究算法再编程实现的流程,最后我发现,先规划好核心算法之后再进行编程实现,比直接进行开发会更加便于优化和检查错误。

posted @ 2018-09-29 13:42  fallenstarowo  阅读(565)  评论(0编辑  收藏  举报