Ответ 1
Вероятно, вы захотите внедрить систему перезаписи терминов . Что касается базовой математики, посмотрите WikiPedia.
Структура модуля перезаписи терминов
Так как я недавно реализовал решение...
-
Сначала подготовьте класс CExpression, который моделирует структуру вашего выражения.
-
Внедрить
CRule
, который содержит шаблон и замену. Используйте специальные символы в качестве переменных шаблона, которые необходимо привязать во время сопоставления шаблонов и заменить в замещающем выражении. -
Затем реализуем класс
CRule
. Основной методapplyRule(CExpression, CRule)
пытается сопоставить правило с любым применимым подвыражением выражения. Если он совпадает, верните результат. -
Завершить, реализовать класс
CRuleSet
, который представляет собой просто набор объектов CRule. Основной методreduce(CExpression)
применяет набор правил до тех пор, пока не может применяться больше правил, а затем возвращается сокращенное выражение. -
Кроме того, вам нужен класс
CBindingEnvironment
, который сопоставляет уже согласованные символы с согласованными значениями.
Попробуйте переписать выражение в нормальную форму
Не забывайте, что этот подход работает до определенного момента, но, вероятно, будет неполным. Это связано с тем, что все следующие правила выполняют локальные перезаписи.
Чтобы сделать эту локальную логику перезаписи более сильной, нужно попытаться преобразовать выражения в то, что я назвал бы нормальной формой. Это мой подход:
-
Если термин содержит литеральные значения, попробуйте перевести этот термин как можно дальше вправо.
-
В конце концов, это буквальное значение может отображаться как можно правее и может быть оценено как часть полностью литерального выражения.
Когда оценивать полностью буквальное выражение
Интересный вопрос заключается в том, когда оценивать полностью буквальное выражение. Предположим, что у вас есть выражение
x * ( 1 / 3 )
который уменьшился бы до
x * 0.333333333333333333
Теперь предположим, что x заменено на 3. Это даст что-то вроде
0.999999999999999999999999
Таким образом, ожидаемая оценка возвращает немного неправильное значение.
С другой стороны, если вы держите (1/3) и сначала замените x на 3
3 * ( 1 / 3 )
правило перезаписи дало бы
1
Таким образом, может быть полезно оценить полностью буквальное выражение в конце.
Примеры правил перезаписи
Вот как мои правила появляются внутри приложения: Символы _1, _2,... соответствуют любому подвыражению:
addRule( new TARuleFromString( '0+_1', // left hand side :: pattern
'_1' // right hand side :: replacement
)
);
или немного сложнее
addRule( new TARuleFromString( '_1+_2*_1',
'(1+_2)*_1'
)
);
Некоторые специальные символы соответствуют только специальным подвыражениям. Например. _Literal1, _Literal2,... соответствуют только буквальным значениям:
addRule( new TARuleFromString( 'exp(_Literal1) * exp(_Literal2 )',
'exp( _Literal1 + _Literal2 )'
)
);
Это правило перемещает нелитеральное выражение влево:
addRule( new TARuleFromString( '_Literal*_NonLiteral',
'_NonLiteral*_Literal'
)
);
Любое имя, начинающееся с символа '_', является переменной шаблона. Хотя система соответствует правилу, она хранит стек назначений уже согласованных символов.
Наконец, не забывайте, что правила могут давать нескончаемые последовательности замены. Таким образом, уменьшая выражение, запомните процесс, какие промежуточные выражения уже были достигнуты ранее.
В моей реализации я не сохраняю промежуточных выражений напрямую. Я сохраняю массив хешей MD5() промежуточного выражения.
Набор правил в качестве отправной точки
Вот набор правил для начала работы:
addRule( new TARuleFromString( '0+_1', '_1' ) );
addRule( new TARuleFromString( '_Literal2=0-_1', '_1=0-_Literal2' ) );
addRule( new TARuleFromString( '_1+0', '_1' ) );
addRule( new TARuleFromString( '1*_1', '_1' ) );
addRule( new TARuleFromString( '_1*1', '_1' ) );
addRule( new TARuleFromString( '_1+_1', '2*_1' ) );
addRule( new TARuleFromString( '_1-_1', '0' ) );
addRule( new TARuleFromString( '_1/_1', '1' ) );
// Rate = (pow((EndValue / BeginValue), (1 / (EndYear - BeginYear)))-1) * 100
addRule( new TARuleFromString( 'exp(_Literal1) * exp(_Literal2 )', 'exp( _Literal1 + _Literal2 )' ) );
addRule( new TARuleFromString( 'exp( 0 )', '1' ) );
addRule( new TARuleFromString( 'pow(_Literal1,_1) * pow(_Literal2,_1)', 'pow(_Literal1 * _Literal2,_1)' ) );
addRule( new TARuleFromString( 'pow( _1, 0 )', '1' ) );
addRule( new TARuleFromString( 'pow( _1, 1 )', '_1' ) );
addRule( new TARuleFromString( 'pow( _1, -1 )', '1/_1' ) );
addRule( new TARuleFromString( 'pow( pow( _1, _Literal1 ), _Literal2 )', 'pow( _1, _Literal1 * _Literal2 )' ) );
// addRule( new TARuleFromString( 'pow( _Literal1, _1 )', 'ln(_1) / ln(_Literal1)' ) );
addRule( new TARuleFromString( '_literal1 = pow( _Literal2, _1 )', '_1 = ln(_literal1) / ln(_Literal2)' ) );
addRule( new TARuleFromString( 'pow( _Literal2, _1 ) = _literal1 ', '_1 = ln(_literal1) / ln(_Literal2)' ) );
addRule( new TARuleFromString( 'pow( _1, _Literal2 ) = _literal1 ', 'pow( _literal1, 1 / _Literal2 ) = _1' ) );
addRule( new TARuleFromString( 'pow( 1, _1 )', '1' ) );
addRule( new TARuleFromString( '_1 * _1 = _literal', '_1 = sqrt( _literal )' ) );
addRule( new TARuleFromString( 'sqrt( _literal * _1 )', 'sqrt( _literal ) * sqrt( _1 )' ) );
addRule( new TARuleFromString( 'ln( _Literal1 * _2 )', 'ln( _Literal1 ) + ln( _2 )' ) );
addRule( new TARuleFromString( 'ln( _1 * _Literal2 )', 'ln( _Literal2 ) + ln( _1 )' ) );
addRule( new TARuleFromString( 'log2( _Literal1 * _2 )', 'log2( _Literal1 ) + log2( _2 )' ) );
addRule( new TARuleFromString( 'log2( _1 * _Literal2 )', 'log2( _Literal2 ) + log2( _1 )' ) );
addRule( new TARuleFromString( 'log10( _Literal1 * _2 )', 'log10( _Literal1 ) + log10( _2 )' ) );
addRule( new TARuleFromString( 'log10( _1 * _Literal2 )', 'log10( _Literal2 ) + log10( _1 )' ) );
addRule( new TARuleFromString( 'ln( _Literal1 / _2 )', 'ln( _Literal1 ) - ln( _2 )' ) );
addRule( new TARuleFromString( 'ln( _1 / _Literal2 )', 'ln( _Literal2 ) - ln( _1 )' ) );
addRule( new TARuleFromString( 'log2( _Literal1 / _2 )', 'log2( _Literal1 ) - log2( _2 )' ) );
addRule( new TARuleFromString( 'log2( _1 / _Literal2 )', 'log2( _Literal2 ) - log2( _1 )' ) );
addRule( new TARuleFromString( 'log10( _Literal1 / _2 )', 'log10( _Literal1 ) - log10( _2 )' ) );
addRule( new TARuleFromString( 'log10( _1 / _Literal2 )', 'log10( _Literal2 ) - log10( _1 )' ) );
addRule( new TARuleFromString( '_Literal1 = _NonLiteral + _Literal2', '_Literal1 - _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = _NonLiteral * _Literal2', '_Literal1 / _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = _NonLiteral / _Literal2', '_Literal1 * _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 =_NonLiteral - _Literal2', '_Literal1 + _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_NonLiteral + _Literal2 = _Literal1 ', '_Literal1 - _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_NonLiteral * _Literal2 = _Literal1 ', '_Literal1 / _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_NonLiteral / _Literal2 = _Literal1 ', '_Literal1 * _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_NonLiteral - _Literal2 = _Literal1', '_Literal1 + _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_NonLiteral - _Literal2 = _Literal1 ', '_Literal1 + _Literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal2 - _NonLiteral = _Literal1 ', '_Literal2 - _Literal1 = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = sin( _NonLiteral )', 'asin( _Literal1 ) = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = cos( _NonLiteral )', 'acos( _Literal1 ) = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = tan( _NonLiteral )', 'atan( _Literal1 ) = _NonLiteral' ) );
addRule( new TARuleFromString( '_Literal1 = ln( _1 )', 'exp( _Literal1 ) = _1' ) );
addRule( new TARuleFromString( 'ln( _1 ) = _Literal1', 'exp( _Literal1 ) = _1' ) );
addRule( new TARuleFromString( '_Literal1 = _NonLiteral', '_NonLiteral = _Literal1' ) );
addRule( new TARuleFromString( '( _Literal1 / _2 ) = _Literal2', '_Literal1 / _Literal2 = _2 ' ) );
addRule( new TARuleFromString( '_Literal*_NonLiteral', '_NonLiteral*_Literal' ) );
addRule( new TARuleFromString( '_Literal+_NonLiteral', '_NonLiteral+_Literal' ) );
addRule( new TARuleFromString( '_Literal1+(_Literal2+_NonLiteral)', '_NonLiteral+(_Literal1+_Literal2)' ) );
addRule( new TARuleFromString( '_Literal1+(_Literal2+_1)', '_1+(_Literal1+_Literal2)' ) );
addRule( new TARuleFromString( '(_1*_2)+(_3*_2)', '(_1+_3)*_2' ) );
addRule( new TARuleFromString( '(_2*_1)+(_2*_3)', '(_1+_3)*_2' ) );
addRule( new TARuleFromString( '(_2*_1)+(_3*_2)', '(_1+_3)*_2' ) );
addRule( new TARuleFromString( '(_1*_2)+(_2*_3)', '(_1+_3)*_2' ) );
addRule( new TARuleFromString( '(_Literal * _1 ) / _Literal', '_1' ) );
addRule( new TARuleFromString( '(_Literal1 * _1 ) / _Literal2', '(_Literal1 * _Literal2 ) / _1' ) );
addRule( new TARuleFromString( '(_1+_2)+_3', '_1+(_2+_3)' ) );
addRule( new TARuleFromString( '(_1*_2)*_3', '_1*(_2*_3)' ) );
addRule( new TARuleFromString( '_1+(_1+_2)', '(2*_1)+_2' ) );
addRule( new TARuleFromString( '_1+_2*_1', '(1+_2)*_1' ) );
addRule( new TARuleFromString( '_literal1 * _NonLiteral = _literal2', '_literal2 / _literal1 = _NonLiteral' ) );
addRule( new TARuleFromString( '_literal1 + _NonLiteral = _literal2', '_literal2 - _literal1 = _NonLiteral' ) );
addRule( new TARuleFromString( '_literal1 - _NonLiteral = _literal2', '_literal1 - _literal2 = _NonLiteral' ) );
addRule( new TARuleFromString( '_literal1 / _NonLiteral = _literal2', '_literal1 * _literal2 = _NonLiteral' ) );
Сделать правила первоклассными выражениями
Интересный момент: поскольку вышеприведенные правила являются специальным выражением, которое правильно оценивается парсером выражений, пользователи могут даже добавлять новые правила и тем самым улучшать возможности перезаписи приложения.
Разбор выражений (или более общих: языки)
Для приложений Cocoa/OBjC, Dave DeLong DDMathParser является идеальным кандидатом для синтаксического анализа математических выражений.
Для других языков наши старые друзья Lex и Yacc или более новый GNU Bison может помочь.
Более молодой и богатый набор готовых к использованию синтаксических файлов, ANTLR - это современный генератор парсеров на основе Java. Помимо чисто командной строки, ANTLRWorks предоставляет интерфейс GUIдля создания и отладки парсеров на основе ANTLR. ANTLR генерирует грамматики для различных языков хоста, например JAVA, C, Python, PHP или С#. В настоящее время среда выполнения ActionScript отключена.
Если вы хотите научиться разбирать выражения (или вообще языки) снизу вверх, я бы предложил этот бесплатный текст книги от Niklaus Wirth (или немецкая книжная версия), известный изобретатель Паскаля и Модулы-2.