Парсер рекурсивного спуска - добавление унифицированных переменных

1

Итак, у меня есть рекурсивный анализатор спуска, который анализирует математическое выражение в infix. Выражение является токенизированным, анализируется вышеупомянутым синтаксическим анализатором, который генерирует AST на лету (с узлами для каждого типа выражения) и оценивает конечное значение. Я обрабатываю все эти значения как doubles; поэтому я использую этот парсер так:

Parser parser = new Parser();

try {
    ExpressionNode expression = parser.parse("5 + 4*cos(pi)");
    System.out.println("The value of the expression is "
            + expression.getValue());
} catch (ParserException | EvaluationException e) {
    System.out.println(e.getMessage());
}

}

С Exceptions я определил себя. Строка expression.getValue() возвращает double, и способ, которым работает мой парсер, заключается в том, что каждый узел выражения возвращает double, поэтому каждая ветвь оценивается снизу вверх, пока она, наконец, не закончит один double ответ.

Дело в том, что я хочу обрабатывать униализированные переменные в моих выражениях, например, если бы я хотел разобрать 5 + x (где x не инициализировался ранее), значение выражения вернет 5 + x.

Должен ли я изменить мой getValue() выражения getValue() для выражения в String? Я чувствую, что это усложнит и раздувает программу, и должен быть лучший способ это сделать. Кто-нибудь имеет опыт такого рода вещей?

Я знаю, что описание моего парсер может быть немного расплывчатым, так это то, где я узнал, как реализовать большинство из них.

  • 0
    Как насчет создания класса «переменная», которая имеет двойное «значение» и логическое «константа», а затем наличие ExpressionNode.getValue () возвращает список этих объектов. Таким образом, ваши 5 + x будут списком из 2 объектов: первый будет иметь значение = 5 и константа = true, второе значение = не имеет значения и константа = false. вам нужно добавить другие вещи, такие как «id» в класс «variable», чтобы идентифицировать переменные
  • 0
    Это имеет смысл, но как бы я сохранил символы?
Показать ещё 2 комментария
Теги:
parsing

1 ответ

2
Лучший ответ

Я предполагаю, что в вашем дереве выражений есть классы, определенные для операторов и констант. Вам нужно будет определить новый класс для переменных.

Затем вам нужно будет добавить метод getAllVariables который может возвращать все переменные ниже любой точки в дереве.

Я предлагаю вам изменить getValue чтобы принять Map<String, Double> чтобы предоставить значения для любых переменных во время оценки. Это нужно будет игнорировать всеми узлами, отличными от переменных, которые возвратят свое собственное значение с карты. Если они не найдут отображение для себя в качестве ключа, они должны выбросить исключение EvaluationException.

Наконец, если вы хотите иметь возможность распечатать выражение в виде строки, то это действительно отдельный метод для getValue. Возможно, getExpressionText. Тогда каждый класс может переопределить это, чтобы вернуть строку, представляющую выражение из этой точки вниз, с переменными, просто возвращающими имя переменной.

Итак, теперь, когда вы проанализировали свое выражение, вы можете получить все переменные, запросить у пользователя значения для них, оценить выражение для заданных значений (перехватить исключение, если они не определены) и распечатать его снова.

ExpressionNode expression = Parser.parse("x + 5 * y");
System.out.println(expression.getExpressionText());
System.out.println(expression.getAllVariables());
Map<String, Double> variableValues = new TreeMap<>();
variableValues.put("x", 4);
variableValues.put("y", -2);
System.out.println("Evaluates to " + expression.getValue(variableValues));

Я бы ожидал, что ваш класс Variable будет выглядеть примерно так:

public class Variable implements ExpressionNode {
    private final String name;

    public double getValue(Map<String, Double> variableValues) {
        if (variableValues.containsKey(name)) {
            return variableValues.get(name);
        } else {
            throw new EvaluationException(name + " is undefined");
        }
    }

    public String getExpressionText() {
        return name;
    }

    public List<String> getAllVariables() {
        return Arrays.asList(name);
    }
}

Еще одна общая операция, которую вы, возможно, захотите выполнить в дереве выражений, - это упростить ее. Это по существу означает оценивать константу все, что можно оценить. На мой взгляд, лучший способ сделать это - вернуть новое упрощенное дерево, а не изменять текущее дерево. Поэтому я рекомендую добавить новый метод в ExpressionNode:

public ExpressionNode simplify();

Для переменных и констант это только вернет this. Для операторов нужно сделать что-то более сложное. Что-то вроде:

class Operator implements ExpressionNode {
    public ExpressionNode simplify() {
    if (getAllVariables().isEmpty()) {
        return new Constant(getValue());
    } else {
        Operator simplified = new Operator(operation);
        for (ExpressionNode operand: operands) {
            simplified.addOperand(operand.simplify());
        }
        return simplified;
    }
}

Надеюсь, вы увидите, что это делает. Если операция может быть полностью оценена, она преобразуется в константу. В противном случае она остается операцией, но каждый из ее операндов упрощается по очереди.

Итак, теперь, если вы хотите упростить выражение, которое вы можете сделать:

System.out.println(Parser.parse("7 * 2 + x * 3").simplify().getExpressionText());

Который вернет "14 + x * 3".

Если вы захотите получить еще более сложный подход, вы сможете повысить осведомленность об ассоциации и распределении в своих операторах и изменить simplify чтобы оно реорганизовывало дерево в группы переменных. Но я считаю, что это выходит за рамки этого вопроса!

  • 0
    Спасибо вам за помощь! У меня уже есть класс переменных, который проверяет, имеет ли переменная установленное значение или нет, и делает то, что мне нужно. Я просто не знаю, как перехватить VariableNotDefinedException, а затем убедиться, что переменная сохраняется в результате.
  • 0
    @Tetramputechture Мне очень жаль, но я не понимаю ваш комментарий. Что вы подразумеваете под «убедитесь, что переменная сохраняется в результате»? Вы имеете в виду в дереве выражений (т.е. парсером) или во время вычисления выражения? Но если вы имеете в виду оценку, то дело не в том, чтобы сохранить переменную в результате, а в том, чтобы заменить ее значением.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню