Search code examples
javamathrandomoperatorsoperator-precedence

Generating random equations with wrong answers ( operator precedence problem )


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<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);
    }

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

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;
                    }
                }
            }

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?


Solution

  • 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("(-?\\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);
    }
    

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

    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();
            }
        }
    }
    

    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.