You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
6.7 KiB
217 lines
6.7 KiB
package model;
|
|
|
|
import java.util.Stack;
|
|
|
|
/**
|
|
* 小学题目生成器,确保题目结果非负
|
|
*/
|
|
public class PrimaryMaker extends Student {
|
|
private static final char[] OPERATORS = {'+', '-', '*', '/'};
|
|
private static final int MIN_OPERANDS = 2;
|
|
private static final int MAX_OPERANDS = 5;
|
|
private static final int MAX_OPERAND_VALUE = 100;
|
|
|
|
private int operandCount;
|
|
private int[] operands;
|
|
private int parenStart;
|
|
private int parenEnd;
|
|
|
|
public PrimaryMaker(String name, String password, String path) {
|
|
super(name, password, path);
|
|
}
|
|
|
|
@Override
|
|
protected String makeOneQuestion() {
|
|
String question;
|
|
while (true) {
|
|
question = generateSingleQuestion();
|
|
if (isQuestionValid(question)) {
|
|
break;
|
|
}
|
|
}
|
|
return question;
|
|
}
|
|
|
|
private String generateSingleQuestion() {
|
|
generateRandomParameters();
|
|
StringBuilder question = new StringBuilder();
|
|
|
|
for (int j = 0; j < operandCount; j++) {
|
|
appendOperandWithParentheses(question, j);
|
|
|
|
if (j < operandCount - 1) {
|
|
appendOperator(question, j);
|
|
}
|
|
}
|
|
return question.toString();
|
|
}
|
|
|
|
private void generateRandomParameters() {
|
|
operandCount = random.nextInt(MAX_OPERANDS - MIN_OPERANDS + 1) + MIN_OPERANDS;
|
|
operands = new int[operandCount];
|
|
|
|
for (int j = 0; j < operandCount; j++) {
|
|
operands[j] = random.nextInt(MAX_OPERAND_VALUE) + 1;
|
|
}
|
|
|
|
int[] parenPos = bracketMaker(operandCount);
|
|
parenStart = parenPos[0];
|
|
parenEnd = parenPos[1];
|
|
}
|
|
|
|
private void appendOperandWithParentheses(StringBuilder question, int index) {
|
|
if (index == parenStart) {
|
|
question.append("( ");
|
|
}
|
|
question.append(operands[index]);
|
|
if (index == parenEnd) {
|
|
question.append(" )");
|
|
}
|
|
}
|
|
|
|
private void appendOperator(StringBuilder question, int currentIndex) {
|
|
char op = OPERATORS[random.nextInt(OPERATORS.length)];
|
|
|
|
if (op == '-') {
|
|
ensureSubtractionIsValid(currentIndex);
|
|
}
|
|
if (op == '/') {
|
|
ensureDivisionIsValid(currentIndex);
|
|
}
|
|
|
|
question.append(" ").append(op).append(" ");
|
|
}
|
|
|
|
private void ensureSubtractionIsValid(int currentIndex) {
|
|
if (operands[currentIndex] < operands[currentIndex + 1]) {
|
|
int temp = operands[currentIndex];
|
|
operands[currentIndex] = operands[currentIndex + 1];
|
|
operands[currentIndex + 1] = temp;
|
|
}
|
|
}
|
|
|
|
private void ensureDivisionIsValid(int currentIndex) {
|
|
if (operands[currentIndex + 1] == 0) {
|
|
operands[currentIndex + 1] = 1;
|
|
}
|
|
}
|
|
|
|
private boolean isQuestionValid(String expression) {
|
|
try {
|
|
evaluateExpression(expression, true);
|
|
return true;
|
|
} catch (ArithmeticException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private double evaluateExpression(String expression, boolean checkNegative)
|
|
throws ArithmeticException {
|
|
Stack<Double> numbers = new Stack<>();
|
|
Stack<Character> ops = new Stack<>();
|
|
|
|
for (int i = 0; i < expression.length(); i++) {
|
|
char c = expression.charAt(i);
|
|
if (c == ' ') {
|
|
continue;
|
|
}
|
|
|
|
if (Character.isDigit(c)) {
|
|
i = parseAndPushNumber(expression, i, numbers);
|
|
} else if (c == '(') {
|
|
ops.push(c);
|
|
} else if (c == ')') {
|
|
processClosingParenthesis(numbers, ops, checkNegative);
|
|
} else if (isOperator(c)) {
|
|
processOperator(c, numbers, ops, checkNegative);
|
|
}
|
|
}
|
|
|
|
processRemainingOperators(numbers, ops, checkNegative);
|
|
|
|
double finalResult = numbers.pop();
|
|
validateResult(finalResult, checkNegative);
|
|
return finalResult;
|
|
}
|
|
|
|
private int parseAndPushNumber(String expression, int startIndex,
|
|
Stack<Double> numbers) {
|
|
StringBuilder sb = new StringBuilder();
|
|
int i = startIndex;
|
|
|
|
while (i < expression.length()
|
|
&& (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
|
|
sb.append(expression.charAt(i++));
|
|
}
|
|
|
|
numbers.push(Double.parseDouble(sb.toString()));
|
|
return i - 1;
|
|
}
|
|
|
|
private void processClosingParenthesis(Stack<Double> numbers, Stack<Character> ops,
|
|
boolean checkNegative) {
|
|
while (ops.peek() != '(') {
|
|
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
|
|
validateResult(result, checkNegative);
|
|
numbers.push(result);
|
|
}
|
|
ops.pop();
|
|
}
|
|
|
|
private void processOperator(char currentOp, Stack<Double> numbers,
|
|
Stack<Character> ops, boolean checkNegative) {
|
|
while (!ops.isEmpty() && hasPrecedence(currentOp, ops.peek())) {
|
|
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
|
|
validateResult(result, checkNegative);
|
|
numbers.push(result);
|
|
}
|
|
ops.push(currentOp);
|
|
}
|
|
|
|
private void processRemainingOperators(Stack<Double> numbers, Stack<Character> ops,
|
|
boolean checkNegative) {
|
|
while (!ops.isEmpty()) {
|
|
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
|
|
validateResult(result, checkNegative);
|
|
numbers.push(result);
|
|
}
|
|
}
|
|
|
|
private void validateResult(double result, boolean checkNegative) {
|
|
if (checkNegative && result < 0) {
|
|
throw new ArithmeticException("Negative result");
|
|
}
|
|
}
|
|
|
|
private boolean isOperator(char c) {
|
|
return c == '+' || c == '-' || c == '*' || c == '/';
|
|
}
|
|
|
|
private boolean hasPrecedence(char op1, char op2) {
|
|
if (op2 == '(' || op2 == ')') {
|
|
return false;
|
|
}
|
|
return (op1 != '*' && op1 != '/') || (op2 != '+' && op2 != '-');
|
|
}
|
|
|
|
private double applyOp(char op, double b, double a) {
|
|
switch (op) {
|
|
case '+':
|
|
return a + b;
|
|
case '-':
|
|
if (a < b) {
|
|
throw new ArithmeticException("Negative result in subtraction");
|
|
}
|
|
return a - b;
|
|
case '*':
|
|
return a * b;
|
|
case '/':
|
|
if (b == 0) {
|
|
throw new ArithmeticException("Cannot divide by zero");
|
|
}
|
|
return a / b;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
} |