生成具有错误答案的随机方程(运算符优先级问题)

huangapple go评论84阅读模式
英文:

Generating random equations with wrong answers ( operator precedence problem )

问题

我正在尝试生成一个随机的方程和它的答案。一切都运行正常,除了答案的计算是错误的(没有考虑操作符的优先级)。我已经尝试修复这个问题有一段时间了,对如何解决它有一个大致的想法,但实际如何做到这一点我一点也不清楚,而且网上几乎没有相关信息。

这是我的代码:

public static Equation randomEquation() {
    Random random = new Random();
    int numNumbers = random.nextInt(3) + 2; // 生成2到4个数字
    List<Integer> numbers = new ArrayList<>();
    for (int i = 0; i < numNumbers; i++) {
        int number = random.nextInt(101); // 生成0到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); // 生成随机操作符:0代表加法,1代表减法,2代表乘法,3代表除法
        int number;
        if (operator == 2) {
            // 限制乘法操作不生成大于10的结果
            do {
                number = random.nextInt(11);
            } while (number == 0);
        } else {
            number = random.nextInt(101); // 生成0到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) {
                    // 如果第二个数字是0或者除法的结果不是整数,重新生成方程
                    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) {
                    // 如果第二个数字是0或者除法的结果不是整数,重新生成方程
                    return randomEquation();
                } else {
                    answer /= number;
                }
            }
        }

我只是在每个数字后附加答案,这在乘法和除法时是不合理的。

所以我的问题是,如何在我的代码中引入操作符优先级的概念?

英文:

I am trying to generate a random Equation and an answer to it.
Everything works fine, except the answers are being calculated wrong ( without consideration of operator precedence ). I've been trying to fix it for some time now, and i have a general idea of how to approach it, but i have no idea how to actually do it, and there is little to no information on it on the web.

Here is my code:

    public static Equation randomEquation() {
Random random = new Random();
int numNumbers = random.nextInt(3) + 2; // generate between 2 and 4 numbers
List&lt;Integer&gt; numbers = new ArrayList&lt;&gt;();
for (int i = 0; i &lt; 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 &lt; 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 -&gt; {
equationBuilder.append(&quot; + &quot;);
answer += number;
}
case 1 -&gt; {
equationBuilder.append(&quot; - &quot;);
answer -= number;
}
case 2 -&gt; {
equationBuilder.append(&quot; * &quot;);
answer *= number;
}
case 3 -&gt; {
equationBuilder.append(&quot; / &quot;);
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);
}

So there is clearly a problem with how the answer is calculated in here:

switch (operator) {
case 0 -&gt; {
equationBuilder.append(&quot; + &quot;);
answer += number;
}
case 1 -&gt; {
equationBuilder.append(&quot; - &quot;);
answer -= number;
}
case 2 -&gt; {
equationBuilder.append(&quot; * &quot;);
answer *= number;
}
case 3 -&gt; {
equationBuilder.append(&quot; / &quot;);
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;
}
}
}

I am simply appending the answer with every number, which does not make sense when multiplying and dividing.

So my question is, how do i introduce the concept of operator precedence into my code?

答案1

得分: 1

为了考虑运算符优先级,不要将方程看作是“以运算符分隔的数字列表”。相反,将其视为一棵

定义一个EquationComponent,它可以是以下之一:

  • 一个常数数字,或
  • 对两个其他EquationComponent进行操作(加法、减法、乘法、除法)

这种封闭的层次结构可以通过封闭接口/类来实现。如果你的Java版本不支持封闭接口/类,那么普通接口也可以。

EquationComponent的特殊之处在于它有一个值,或者你可以称之为“答案”。

要从常数数字中获取“答案”,只需返回该数字。要从操作中获取“答案”,首先从操作的两侧获取“答案”,然后将运算符应用于这两个值。

当我们将EquationComponent转换为字符串时,我们总是在其周围添加括号。因为“答案”是基于方程组件计算的,这确保了答案始终与方程的显示方式保持一致。

以下是一个示例实现。请注意toStringgetAnswer的实现方式。

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到3个操作
    EquationComponent eq = makeEquation();
    System.out.println(eq);
    System.out.println(eq.getAnswer());
}

private static EquationComponent makeEquation() {
    if (remainingOperations > 0) {
        remainingOperations--;
        // 即使你的Java版本不支持switch表达式,这也应该很明显它是做什么的,
        // 并且将其转换为switch语句也很简单。
        Operation op = switch (r.nextInt(4)) {
            case 0 -> Operation.PLUS;
            case 1 -> Operation.MINUS;
            case 2 -> Operation.TIMES;
            default -> Operation.DIVIDE;
        };
        if (op == Operation.TIMES) {
            return new Equation(
                    // 左侧可以是另一个数字
                    r.nextBoolean() ? makeEquation() : makeNumber(101),
                    // 如果是乘法,生成一个小数值作为右侧操作数
                    makeNumber(11),
                    op
            );
        } else {
            // 这允许两侧都是常数数字,
            // 这意味着我们可以生成具有较少操作的方程
            return new Equation(
                    // 左侧可以是另一个数字
                    r.nextBoolean() ? makeEquation() : makeNumber(101),
                    // 右侧可以是另一个数字
                    r.nextBoolean() ? makeEquation() : makeNumber(101),
                    op
            );
        }
    } else {
        return makeNumber(101);
    }
}

private static ConstantNumber makeNumber(int limit) {
    return new ConstantNumber(r.nextInt(limit));
}
英文:

To account for operator precedence, don't think of an equation as "a list of numbers separated as operators". Instead, think of it as a tree.

Define a EquationComponent, which can be either:

  • a constant number, or
  • an operation (plus, minus, times, divide) on two other EquationComponents

This kind of sealed hierarchy can be implemented by a sealed interface/class. If your version of Java doesn't have that, a regular interface is fine too.

The special thing that an EquationComponent can do, is it has a value, or as you'd like to call it, an "answer".

To get an "answer" from a constant number, just return that number. To get an "answer" from an operation, first get the "answers" from the two sides of the operation, and apply the operator to the two values.

When we convert an EquationComponent to string, we always add parentheses around it. And because the "answer" is calculated based on the equation components, this makes sure that the answer is always consistent with how the equation is displayed.

Here is an example implementation. Pay attention to how toString and getAnswer are implemented.

enum Operation {
PLUS(&quot;+&quot;),
MINUS(&quot;-&quot;),
TIMES(&quot;*&quot;),
DIVIDE(&quot;/&quot;);
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&#39;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 &quot;(&quot; + left + &quot; &quot; + operation + &quot; &quot; + right + &quot;)&quot;;
}
@Override
public int getAnswer() {
// this could be cached somewhere...
return switch (operation) {
case PLUS -&gt; left.getAnswer() + right.getAnswer();
case MINUS -&gt; left.getAnswer() - right.getAnswer();
case TIMES -&gt; left.getAnswer() * right.getAnswer();
// integer division?
case DIVIDE -&gt; left.getAnswer() / right.getAnswer();
};
}
}

Now we have the infrastructure, we can start generating equations. Instead of deciding how many numbers you are going to generate. Decide how many operations you are going to generate.

Write a method that generates an operation. This method will choose an operator, and then for the left and right side of it, call itself recursively to generate more operations, or constant numbers if the maximum number of operations is already generated.

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 &gt; 0) {
remainingOperations--;
// even if your version of Java doesn&#39;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 -&gt; Operator.PLUS;
case 1 -&gt; Operator.MINUS;
case 2 -&gt; Operator.TIMES;
default -&gt; 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));
}

答案2

得分: 1

由于_operator_是随机生成的,您将不得不在其生成后评估_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);
}

此外,我修改了您的_operator_分配代码块,如下所示。

switch (operator) {
    case 0 -> equationBuilder.append(" + ");
    case 1 -> equationBuilder.append(" - ");
    case 2 -> equationBuilder.append(" * ");
    case 3 -> {
        equationBuilder.append(" / ");
        if (number == 0 || answer % number != 0) {
            // 如果第二个数字为0或除法结果不是整数,则重新生成方程
            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

此外,我还添加了一些_println_调用到_while循环_中,以显示过程。

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

此外,您还可以通过使用_double_并调整正则表达式以匹配实数来改进此代码。

或者,您还可以使用_BigDecimal_类来计算_64位_范围之外的数字。

英文:

Since the operator is generated randomly, you'll have to evaluate the equationBuilder string, after its generation.

In this example I utilized the Pattern and Matcher classes to isolate the operation indices, using a regular expression pattern.

static int answer(String string) {
    Pattern pattern = Pattern.compile(&quot;(-?\\d+) \\* (-?\\d+)&quot;);
    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(&quot;(-?\\d+) / (-?\\d+)&quot;);
    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(&quot;(-?\\d+) ([-+]) (-?\\d+)&quot;);
    while ((matcher = pattern.matcher(string)).find()) {
        operandA = Integer.parseInt(matcher.group(1));
        operandB = Integer.parseInt(matcher.group(3));
        result = switch (matcher.group(2)) {
            case &quot;+&quot; -&gt; operandA + operandB;
            default -&gt; operandA - operandB;
        };
        string = string.replace(matcher.group(), String.valueOf(result));
    }
    return Integer.parseInt(string);
}

And, I modified your operator assignment code-block, with the following.

switch (operator) {
    case 0 -&gt; equationBuilder.append(&quot; + &quot;);
    case 1 -&gt; equationBuilder.append(&quot; - &quot;);
    case 2 -&gt; equationBuilder.append(&quot; * &quot;);
    case 3 -&gt; {
        equationBuilder.append(&quot; / &quot;);
        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();
        }
    }
}

And, subsequently, returned this as the value.

return new Equation(equation, answer(equation));

Here are a few examples.

6 * 5 - 34 - 59 = -63
8 * 1 = 8
5 * 7 = 35
71 - 22 = 49
43 - 52 = -9

Additionally, I added some println calls to the while-loops, to display the procedures.

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

Furthermore, you could improve this code by utilizing a double, and adjusting the regular expression to match real numbers.

Or even, utilize the BigDecimal class to evaluate numbers outside of the 64-bit range.

huangapple
  • 本文由 发表于 2023年6月19日 18:53:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76505936.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定