Node-JS Zhang Wenjun & Jian Huilan

Paired items: Four arithmetic problem generators

Basic Information

github address: https://github.com/sliyoxn/questionGenerator

Online preview: https://sliyoxn.github.io/questionGenerator/

Author: Software Engineering 18 (1) Ban Zhang Wenjun 3218004986 && Jane Cymbidium 3218004992 (SakuraSnow && Maxwell_Who)

Tip: After opening the drawer, press ESC to close

Interface preview:

Performance analysis

The following table shows the time required to calculate x expressions (green line) and the time required to generate 10,000 questions (blue line).

Algorithm improvements and page performance improvements took about 8 hours

Algorithm improvements

  1. Use Polish expression && instead for evaluation
  2. Use more random options to generate more meta-expressions

Page performance improvements

  1. Use worker to start multithreading, squeeze CPU and prevent main thread (page) from getting stuck
  2. When calculating large-scale data (for example, when generating 1w + questions and judging whether a large number of questions are right or wrong), load the data in batches to prevent long waiting times

Before

using Polish expressions : After using Polish expressions

Page performance improvement

The purpose of using the worker is to prevent the page from completely blocking when loading 1w + data (about 2s from the data)
. The purpose of using batch loading is to see the data displayed on the page within 500ms

Code

Randomly generated expressions

// from,to表示生成随机数的范围, count表示生成的题目数量, 
// maxLoopCount是如果发现题目重复或者运算过程中出现负数允许的最大重试次数
// simpleExpressSet是已经有的题目的set
function generateTopic({from, to, count, maxLoopCount = count * 5, simpleExpressionSet} ) {
	let str = "";
	let answerArr = [];
	let simpleExpression = "";
	for (let i = 0; i < count; i++) {
		// 生成单条题目
		let expressionObj = getExpression(from, to);
		let expression = expressionObj.expression;
		// 生成的题目里有没有负子项
		let hasNegativeNumber = expressionObj.hasNegativeNumber;
		simpleExpression = expressionObj.simpleExpression;
		let curLoopCount = 0;
		let calRes = calEval(expression);
		// 如果生成的有负子项就重新获取
		while ((hasNegativeNumber === true || calRes.hasNegativeNumber || simpleExpressionSet.has(simpleExpression)) && curLoopCount < maxLoopCount) {
			expressionObj = getExpression(from, to);
			expression = expressionObj.expression;
			simpleExpression = expressionObj.simpleExpression;
			hasNegativeNumber = expressionObj.hasNegativeNumber;
			calRes = calEval(expression);
			curLoopCount ++;
		}
		// 防止死循环,设置最大重试次数
		if (maxLoopCount <= curLoopCount) {
			return {
				text : str.slice(0, str.length - 1),
				answer : answerArr,
				warnMsg : "重试次数已达最大, 生成停止, 共计生成" + i + "题"
			}
		}
		str += expression;
		answerArr.push(calRes.val);
		if (simpleExpression !== "") {
			simpleExpressionSet.add(simpleExpression);
		}
		str += "\n";
	}
	return {
		text : str.slice(0, str.length - 1),
		answer : answerArr,
		simpleExpressionSet
	}
}

// 获取单个表达式
function getExpression(from, to) {
	let expression = '';
        // 随机操作数
	let leftVal = getRandomOperand(from, to);
        // 随机生成符号
	let operator = getRandomOperator();
        // 随机生成操作符的个数
	let operatorCount = getRandomOperatorCount();
        // 随机判断是否使用首位括号
	let useFirstIndexBracket = !!getRandom(0,1) && operatorCount >= 1;
	let firstIndexBracketIndex = getRandom(1,operatorCount - 1);
	let operandArr = [leftVal];
	let operatorArr = [operator];
        // 根据情况拼接字符串完成生成
	if (useFirstIndexBracket && operatorCount >= 2) {
		expression += `(${leftVal} ${operator} `;
		operator = getRandomOperator();
		operatorArr.push(operator);
		expression += `${randomExpression(from, to, firstIndexBracketIndex - 1, operandArr, operatorArr)}) ${operator} `;
		expression += `${randomExpression(from, to, operatorCount - firstIndexBracketIndex - 1, operandArr, operatorArr)}`
	} else {
		expression += `${leftVal} ${operator} `;
		expression += `${randomExpression(from, to, operatorCount - 1, operandArr, operatorArr)}`
	}
        // 获取simpleExpression用于判定是否重复
	let simpleExpression = getSimpleExpression(operandArr, operatorArr);
	return {
		expression,
		simpleExpression
	}
}

/**
 * 递归生成表达式
 * @param {Number} from
 * @param {Number} to
 * @param {Number} remain
 * @param {Array} operandArr
 * @param {Array} operatorArr
 * @param {Object} hasNegativeNumberObj 一个含有hasNegativeNumber标识的对象
 */
function randomExpression(from, to, remain, operandArr, operatorArr, hasNegativeNumberObj) {

	let leftVal = getRandomOperand(from, to);
	let useBracket = !!getRandom(0,1);
	operandArr.push(leftVal);
	if (remain) {
		let operator = getRandomOperator();
		operatorArr.push(operator);
		// rightExpress是一个表达式,可以计算是否为负数
		let rightExpress = randomExpression(from, to, remain - 1, operandArr, operatorArr, hasNegativeNumberObj);
		// 如果计算的是负数,就把标志位置为true
		if (calEval(`${leftVal} ${operator} ${rightExpress}`).hasNegativeNumber) {
			hasNegativeNumberObj.hasNegativeNumber = true;
		}
		if (useBracket) {
			return `(${leftVal} ${operator} ${rightExpress})`;
		} else {
			return  `${leftVal} ${operator} ${rightExpress}`
		}
	} else {
		return leftVal;
	}

}



Calculation expression

function calEval(eval) {
        // 中缀转后缀
	let expression = transform(eval);
	let operandStack = new Stack();
	let array = expression.split(" ");
	let hasNegativeNumber = false;
        // 用栈进行后缀表达式的处理
	while (array.length) {
		let o = array.shift();
		if (operandExp.test(o)) {
			operandStack.push(o);
		} else {
			let a = operandStack.pop();
			let b = operandStack.pop();
			let res = Fraction.calculate(b, a, o);
			if (res.value < 0 || res.value === Infinity) {
				hasNegativeNumber = true;
				return {
					val: res.value === Infinity ? Infinity : "-99.99",
					hasNegativeNumber
				}
			}
			operandStack.push(res);
		}
	}
        // 把结果丢回去
	return {
		val : operandStack.pop().toMixedString(),
		hasNegativeNumber
	}
};

/**
 * 中缀转后缀
 * @param {String} string
 */
function transform(string) {
	let expression = "";
	let operatorStack = new Stack();
	while ((string = string.trim()) !== "") {
		let operandTestRes = string.match(operandExp);
		let operatorTestRes = string.match(operatorExp);
		let isOperand = operandTestRes && (operandTestRes.index === 0);
		let isOperator = operatorTestRes && (operatorTestRes.index === 0);
                // 判断是操作数还是操作符
		if (isOperand) {
			let matchStr = operandTestRes[0];
			expression += matchStr + " ";
			string = string.slice(operandTestRes.index + matchStr.length);
		} else if (isOperator) {
			let operator = string[0];
			let topOperator = null;
                        // 对不同操作符进行处理
			switch (operator) {
				case "+":
				case "-":
				case "*":
				case "/":
					topOperator = operatorStack.getTop();
					if (topOperator) {
						while (!operatorStack.isEmpty() && !comparePriority((topOperator = operatorStack.getTop()), operator)) {
							expression += operatorStack.pop() + " ";
						}
						operatorStack.push(operator);
					} else {
						operatorStack.push(operator);
					}
					break;
				case "(":
					operatorStack.push(operator);
					break;
				case ")":
					while ((topOperator = operatorStack.getTop()) !== "(") {
						expression += operatorStack.pop() + " ";
					}
					operatorStack.pop();
					break;

			}
			string = string.slice(1);
		}
	}
	while (!operatorStack.isEmpty()) {
		expression += operatorStack.pop() + " ";
	}
	expression = expression.trim();
	return expression;
}

Test run

Test calEval


The test cases of these calculation expressions cover most of the test cases, so the probability of credibility is large
. See details in test / index.js

Test generation questions

Question

Answer

Sampling and reviewing the generated questions, the result is correct, so the probability of credibility is large
. See details in test / index.js

Judgment

Modify some answers, the test can identify errors and inconsistencies, so the probability of credibility is greater

PSP

PSP Personal Software Process Stages Estimated time (minutes) Actual time (minutes)
Planning plan 15 60
· Estimate Estimate how much time this task will take 5 15
Development Development 120 360
Analysis Needs analysis (including learning new technologies) 5 5
·Design Spec Generate design documents 20 30
·Design Review Design review (review design documents with colleagues) 60 120
· Coding Standard Code specifications (making appropriate specifications for current development) 10 20
·Design Specific design 60 40
·Coding Specific coding 100 180
·Code Review Code review 120 480
·Test Test (self-test, modify code, submit changes) 30 20
Reporting report 10 60
·Test Report testing report 10 10
·Size Measurement Calculation workload 10 10
·Postmortem & Process Improvement Plan Summarize afterwards and propose a process improvement plan 30 60
total 575 1470

Project summary

Success & Loss && Sharing Experience

This pairing project takes a long time, mainly because the two people need a long break-in time because of their disparity in strength, and the division of labor is also chaotic, because one of the parties is too bad and most of the work is done by the other party. The chicken only implemented one or two small functions and changed some minor bugs ...
but at least the end result was very good.

Pair feeling

Zhang Wenjun: It's
difficult to make a pair at the beginning, and it is often difficult for the other party to keep up because of his own jumping in thinking. At this time, it is necessary to stop and explain the code, which leads to the idea being easily interrupted. It is often explained that half of the time when things are not right, you can change the implementation or find potential bugs.

After the project is used to, there will be many convenient places. For example, the other party ’s English is better. It saves me the time to open Google Translator to find the translation. It can also correct some strange spelling. When I know that the other party will see my code, I will be in Subconsciously, the code is written more concisely and readable, and the quality of the code is virtually improved, and when debugging, the other party is very patient and analyzes the code with me, making it easier for me to find problems when explaining my own code (little yellow duck debug method), in the later stage of the project, when discussing optimization and improving ideas together, the other party gave a lot of ideas, such as opening multiple threads, batch updates, displaying progress bars, using Polish expressions, etc.

Jian Huilan:
I am full of apologies to the big brother, and I am very grateful to the big brother for taking me to do project assignments. I swear I must study hard not to do crane tail orz

Because my strength is no better than that of the gangster project, I have experienced the general feeling of going to an internship. After preliminary discussion and planning, the gangster has also initially realized my level and tearfully (?) Said that he does not intend to give up , I recommended a lot of related resources to me one by one, let me review a wave (consolidated the foundation), seeing the big guys clean and clear code, I also felt a studio-level code specification.

With the development of the project schedule of my role gradually approaches odd jobs , gradually diversify my work, to experience the basic idea to implement to correct small details wrong to various aspects of intense discussion, let me more insight into The charm of programming and the greatness of programmers!

Guess you like

Origin www.cnblogs.com/maxwell-who/p/12695347.html