提问人:Loso 提问时间:6/19/2023 更新时间:6/20/2023 访问量:63
生成具有错误答案的随机方程(运算符优先级问题)
Generating random equations with wrong answers ( operator precedence problem )
问:
我正在尝试生成一个随机方程及其答案。 一切正常,除了答案计算错误(不考虑运算符优先级)。一段时间以来,我一直在尝试修复它,我对如何处理它有一个大致的了解,但我不知道如何实际操作,而且网络上几乎没有关于它的信息。
这是我的代码:
public static Equation randomEquation() {
Random random = new Random();
int numNumbers = random.nextInt(3) + 2; // generate between 2 and 4 numbers
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < numNumbers; i++) {
int number = random.nextInt(101); // generate random number between 0 and 100
numbers.add(number);
}
StringBuilder equationBuilder = new StringBuilder();
int answer = numbers.get(0);
equationBuilder.append(numbers.get(0));
for (int i = 1; i < numNumbers; i++) {
int operator = random.nextInt(4); // generate random operator: 0 for addition, 1 for subtraction, 2 for multiplication, 3 for division
int number;
if (operator == 2) {
// Limit multiplication operation to never generate a result more than 10
do {
number = random.nextInt(11);
} while (number == 0);
} else {
number = random.nextInt(101); // generate random number between 0 and 100
}
switch (operator) {
case 0 -> {
equationBuilder.append(" + ");
answer += number;
}
case 1 -> {
equationBuilder.append(" - ");
answer -= number;
}
case 2 -> {
equationBuilder.append(" * ");
answer *= number;
}
case 3 -> {
equationBuilder.append(" / ");
if (number == 0 || answer % number != 0) {
// If the second number is 0 or the division results in a non-integer answer, regenerate the equation
return randomEquation();
} else {
answer /= number;
}
}
}
equationBuilder.append(number);
}
String equation = equationBuilder.toString();
return new Equation(equation, answer);
}
因此,这里的答案显然存在问题:
switch (operator) {
case 0 -> {
equationBuilder.append(" + ");
answer += number;
}
case 1 -> {
equationBuilder.append(" - ");
answer -= number;
}
case 2 -> {
equationBuilder.append(" * ");
answer *= number;
}
case 3 -> {
equationBuilder.append(" / ");
if (number == 0 || answer % number != 0) {
// If the second number is 0 or the division results in a non-integer answer, regenerate the equation
return randomEquation();
} else {
answer /= number;
}
}
}
我只是在每个数字后面附加答案,这在乘法和除法时没有意义。
所以我的问题是,如何将运算符优先级的概念引入我的代码中?
答:
为了说明运算符的优先级,不要将等式视为“作为运算符分隔的数字列表”。相反,把它想象成一棵树。
定义一个 ,它可以是:EquationComponent
- 常数,或
- 对另外两个 S 的运算(加号、减号、乘号、除号)
EquationComponent
这种密封的层次结构可以由密封的接口/类来实现。如果你的 Java 版本没有这个功能,那么常规接口也可以。
一个可以做的特别之处在于它有一个价值,或者你想称之为“答案”。EquationComponent
要从常数中获得“答案”,只需返回该数即可。若要从操作中获取“答案”,首先从操作的两端获取“答案”,然后将运算符应用于这两个值。
当我们将 an 转换为字符串时,我们总是在它周围添加括号。而且,由于“答案”是根据方程式分量计算的,因此可以确保答案始终与方程式的显示方式一致。EquationComponent
下面是一个示例实现。注意如何实施和实施。toString
getAnswer
enum Operation {
PLUS("+"),
MINUS("-"),
TIMES("*"),
DIVIDE("/");
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
}
sealed interface EquationComponent permits Equation, ConstantNumber {
int getAnswer();
}
final class ConstantNumber implements EquationComponent {
private final int number;
// getters, equals, hashCode if you need those...
public ConstantNumber(int number) {
this.number = number;
}
@Override
public String toString() {
return Integer.toString(number);
}
@Override
public int getAnswer() {
return number;
}
}
final class Equation implements EquationComponent {
private final EquationComponent left;
private final EquationComponent right;
private final Operation operation;
// getters, equals, hashCode if you need those...
public Equation(EquationComponent left, EquationComponent right, Operation operation) {
this.left = left;
this.right = right;
this.operation = operation;
}
// I'm using string concatenation here just for convenience,
// you can totally change this to use StringBuilder instead.
@Override
public String toString() {
// note that this adds parentheses to every operation
return "(" + left + " " + operation + " " + right + ")";
}
@Override
public int getAnswer() {
// this could be cached somewhere...
return switch (operation) {
case PLUS -> left.getAnswer() + right.getAnswer();
case MINUS -> left.getAnswer() - right.getAnswer();
case TIMES -> left.getAnswer() * right.getAnswer();
// integer division?
case DIVIDE -> left.getAnswer() / right.getAnswer();
};
}
}
现在我们有了基础设施,我们可以开始生成方程式了。而不是决定你要生成多少个数字。确定要生成多少个操作。
编写生成操作的方法。此方法将选择一个运算符,然后对于它的左侧和右侧,递归调用自身以生成更多操作,如果已经生成了最大操作数,则调用常量数。
private static int remainingOperations;
private static final Random r = new Random();
public static void main(String[] value) throws IOException {
remainingOperations = r.nextInt(1, 4); // 1 to 3 operations
EquationComponent eq = makeEquation();
System.out.println(eq);
System.out.println(eq.getAnswer());
}
private static EquationComponent makeEquation() {
if (remainingOperations > 0) {
remainingOperations--;
// even if your version of Java doesn't have switch expressions
// it should be obvious what this does and converting it to a switch
// statement is trivial.
Operator op = switch (r.nextInt(4)) {
case 0 -> Operator.PLUS;
case 1 -> Operator.MINUS;
case 2 -> Operator.TIMES;
default -> Operator.DIVIDE;
};
if (op == Operator.TIMES) {
return new Operation(
// the left could be another number
r.nextBoolean() ? makeEquation() : makeNumber(101),
// generate a small number on the right if it is multiplication
makeNumber(11),
op
);
} else {
// this allows both sides to be a constant number,
// which means we could generate an equation with fewer
// operations than remainingOperations
return new Operation(
// the left could be another number
r.nextBoolean() ? makeEquation() : makeNumber(101),
// the right could be another number
r.nextBoolean() ? makeEquation() : makeNumber(101),
op
);
}
} else {
return makeNumber(101);
}
}
private static ConstantNumber makeNumber(int limit) {
return new ConstantNumber(r.nextInt(limit));
}
由于运算符是随机生成的,因此必须在生成 equationBuilder 字符串后对其进行评估。
在此示例中,我利用 Pattern 和 Matcher 类,使用正则表达式模式来隔离运算索引。
static int answer(String string) {
Pattern pattern = Pattern.compile("(-?\\d+) \\* (-?\\d+)");
Matcher matcher;
int operandA, operandB, result;
while ((matcher = pattern.matcher(string)).find()) {
operandA = Integer.parseInt(matcher.group(1));
operandB = Integer.parseInt(matcher.group(2));
result = operandA * operandB;
string = string.replace(matcher.group(), String.valueOf(result));
}
pattern = Pattern.compile("(-?\\d+) / (-?\\d+)");
while ((matcher = pattern.matcher(string)).find()) {
operandA = Integer.parseInt(matcher.group(1));
operandB = Integer.parseInt(matcher.group(2));
result = operandA / operandB;
string = string.replace(matcher.group(), String.valueOf(result));
}
pattern = Pattern.compile("(-?\\d+) ([-+]) (-?\\d+)");
while ((matcher = pattern.matcher(string)).find()) {
operandA = Integer.parseInt(matcher.group(1));
operandB = Integer.parseInt(matcher.group(3));
result = switch (matcher.group(2)) {
case "+" -> operandA + operandB;
default -> operandA - operandB;
};
string = string.replace(matcher.group(), String.valueOf(result));
}
return Integer.parseInt(string);
}
而且,我修改了您的运算符分配代码块,如下所示。
switch (operator) {
case 0 -> equationBuilder.append(" + ");
case 1 -> equationBuilder.append(" - ");
case 2 -> equationBuilder.append(" * ");
case 3 -> {
equationBuilder.append(" / ");
if (number == 0 || answer % number != 0) {
// If the second number is 0 or the division results in a non-integer answer, regenerate the equation
return randomEquation();
}
}
}
随后,将其作为值返回。
return new Equation(equation, answer(equation));
这里有几个例子。
6 * 5 - 34 - 59 = -63
8 * 1 = 8
5 * 7 = 35
71 - 22 = 49
43 - 52 = -9
此外,我还向 while-loops 添加了一些 println 调用,以显示过程。
15 * 5
15 * 5 = 75
100 + 47 - 40 + 67
100 + 47 = 147
147 - 40 = 107
107 + 67 = 174
87 / 1 * 4
1 * 4 = 4
87 / 4 = 21
22 * 6 - 37
22 * 6 = 132
132 - 37 = 95
17 - 44 + 45 * 6
45 * 6 = 270
17 - 44 = -27
-27 + 270 = 243
此外,您可以通过使用双精度值并调整正则表达式以匹配实数来改进此代码。
甚至,利用 BigDecimal 类来计算 64 位范围之外的数字。
上一个:数学 ;数学随机
评论