Итак, у меня есть рекурсивный анализатор спуска, который анализирует математическое выражение в 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
? Я чувствую, что это усложнит и раздувает программу, и должен быть лучший способ это сделать. Кто-нибудь имеет опыт такого рода вещей?
Я знаю, что описание моего парсер может быть немного расплывчатым, так это то, где я узнал, как реализовать большинство из них.
Я предполагаю, что в вашем дереве выражений есть классы, определенные для операторов и констант. Вам нужно будет определить новый класс для переменных.
Затем вам нужно будет добавить метод 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
чтобы оно реорганизовывало дерево в группы переменных. Но я считаю, что это выходит за рамки этого вопроса!