Я написал этот eval метод для арифметических выражений, чтобы ответить на этот вопрос. Он выполняет сложение, вычитание, умножение, деление, возведение в степень (используя ^ символ) и несколько базовых функций, таких как sqrt. Он поддерживает группировку с использованием (...), и он получает правильные правила приоритета операторов и ассоциативности.
Анализатор является анализатором рекурсивного спуска, поэтому внутренне использует отдельные методы синтаксического анализа для каждого уровня приоритета операторов в своей грамматике. Я намеренно сохранил его коротким, но вот несколько идей, которыми вы, возможно, захотите дополнить его:
Переменные:
Бит синтаксического анализатора, считывающий имена функций, можно легко изменить и для обработки пользовательских переменных, выполнив поиск имен в таблице переменных, переданной методу eval, например, a Map<String,Double> variables.
Отдельная компиляция и вычисление:
Что, если, добавив поддержку переменных, вы захотите вычислять одно и то же выражение миллионы раз с измененными переменными, не разбирая его каждый раз? Это возможно. Сначала определите интерфейс, который будет использоваться для вычисления предварительно скомпилированного выражения:
Теперь, чтобы переработать исходную функцию "eval" в функцию "parse", измените все методы, которые возвращают doubles, чтобы вместо этого они возвращали экземпляр этого интерфейса. Для этого хорошо подходит лямбда-синтаксис Java 8. Пример одного из измененных методов:
Expression parseExpression() { Expressionx= parseTerm(); for (;;) { if (eat('+')) { // addition Expressiona= x, b = parseTerm(); x = (() -> a.eval() + b.eval()); } elseif (eat('-')) { // subtraction Expressiona= x, b = parseTerm(); x = (() -> a.eval() - b.eval()); } else { return x; } } }
Который строит рекурсивное дерево из Expression объектов, представляющих скомпилированное выражение (абстрактное синтаксическое дерево). Затем вы можете скомпилировать его один раз и повторно вычислять с разными значениями.:
Вместо double вы могли бы изменить вычислитель, чтобы использовать что-то более мощное, например BigDecimal, или класс, который реализует комплексные числа или рациональные числа (дроби). Вы могли бы даже использовать Object, допуская некоторое сочетание типов данных в выражениях, как в реальном языке программирования. :)
Для моего университетского проекта я искал анализатор / вычислитель, поддерживающий как базовые формулы, так и более сложные уравнения (особенно повторяющиеся операторы). Я нашел очень хорошую библиотеку с открытым исходным кодом для JAVA и .NET под названием mXparser. Я приведу несколько примеров, чтобы составить некоторое представление о синтаксисе, для получения дальнейших инструкций, пожалуйста, посетите веб-сайт проекта (особенно раздел "Руководство").
Expressione=newExpression("sum( i, 1, 100, sin(i) )"); doublev= e.calculate()
Найдено недавно - на случай, если вы хотите попробовать синтаксис (и посмотреть расширенный вариант использования), вы можете загрузить приложение для скалярногокалькулятора, работающее на базе mXparser.
Ответ 4
Правильный способ решить эту проблему - использовать лексер и синтаксический анализатор. Вы можете написать простые версии этих выражений самостоятельно, или на этих страницах также есть ссылки на Java-лексеры и синтаксические анализаторы.
Создание анализатора рекурсивного спуска - действительно хорошее учебное упражнение.