Возможно, вы захотите внедрить систему переписывания терминов .Что касается базовой математики, взгляните на 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 , DDMathParser Дэйва ДеЛонга является идеальным кандидатом для синтаксического анализа математических выражений.
Для других языков наши старые друзья Lex & Yacc или более новые GNU Bison могут помочь.
Намного моложе и с Огромный набор готовых к использованию синтаксических файлов , ANTLR - это современный генератор синтаксических анализаторов, основанный на Java.Помимо чисто командной строки, ANTLRWorks предоставляет интерфейс GUI для создания и отладки синтаксических анализаторов на основе ANTLR.ANTLR генерирует грамматики для различного языка хоста , например JAVA, C, Python, PHP или C # .В настоящее время среда выполнения ActionScript не работает .
Если вы хотите узнать, как анализировать выражения (или языки в целом) снизу вверх, япредложил бы бесплатный текст книги от Никлауса Вирта (или немецкого книжного издания ), известного изобретателя Паскаля и Модула-2.