简化mathexpression式的策略

我有一个结构良好的树,代表一个mathexpression式。 例如,给定string: "1+2-3*4/5" 4/5 "1+2-3*4/5" ,将其parsing为:

 subtract(add(1,2),divide(multiply(3,4),5)) 

这是表示为这棵树:

“1 + 2-3 * 4/5”

我想要做的就是拿走这棵树,尽可能减less它。 在上面的例子中,这很简单,因为所有的数字都是常量。 然而,一旦我允许未知数(用$表示,后跟一个标识符),情况就会变得更加复杂:

"3*$a/$a"变成divide(multiply(3,$a), $a)

这应该简化为3 ,因为$a条款应该互相取消。 问题是,“我怎样才能以通用的方式认识到这一点?” 我如何认识到min(3, sin($x))总会是sin($x) ? 如何识别sqrt(pow($a, 2))是否是abs($a) ? 我怎样才能认识到nthroot(pow(42, $a), $a) (42 次方的根)是42

我意识到这个问题是相当广泛的,但是我一直在反对这个问题一段时间,还没有拿出足够满意的东西。

你可能想要实现一个术语重写系统 。 关于math基础,看看WikiPedia 。

术语重写模块的结构

由于我最近实施了解决scheme…

  • 首先,准备一个类CExpression,它模拟expression式的结构。

  • 执行包含模式和replace的CRule 。 使用特殊符号作为模式variables,在模式匹配期间需要进行绑定,并在replaceexpression式中进行replace。

  • 然后,实现一个类CRule 。 这是applyRule(CExpression, CRule)的主要方法applyRule(CExpression, CRule)试图将规则与任何适用的expression式子expression式进行匹配。 如果匹配,则返回结果。

  • 最后,实现一个CRuleSet类,它只是一组CRule对象。 主要方法reduce(CExpression)只要不应用更多的规则就可以应用规则集,然后返回缩减的expression式。

  • 此外,您还需要一个CBindingEnvironment类,它将已匹配的符号映射到匹配的值。

尝试将expression式重写为正常forms

不要忘记,这种方法有一定的作用,但可能是不完整的。 这是由于以下规则执行本地术语重写。

为了使这个本地重写逻辑更强大,我们应该尝试将expression式转换成我称之为正常forms的东西。 这是我的做法:

  • 如果一个术语包含文字值,尽量将该术语尽可能地移到最右边。

  • 最终,这个文字值可能出现在最右边,可以作为完整文字expression式的一部分进行评估。

何时评估完整的文字expression

一个有趣的问题是什么时候评估完整的文字expression。 假设你有一个expression式

  x * ( 1 / 3 ) 

这将减less到

  x * 0.333333333333333333 

现在假设x被replace为3.这将产生类似的东西

  0.999999999999999999999999 

因此,急切的评估返回一个稍微不正确的值。

另一方面,如果你保留(1/3)并且先把xreplace成3

  3 * ( 1 / 3 ) 

重写规则会给

  1 

因此,迟到评估完全文字expression可能是有用的。

重写规则的例子

这是我的规则如何出现在应用程序中:_1,_2,…符号匹配任何子expression式:

 addRule( new TARuleFromString( '0+_1', // left hand side :: pattern '_1' // right hand side :: replacement ) ); 

或者更复杂一点

 addRule( new TARuleFromString( '_1+_2*_1', '(1+_2)*_1' ) ); 

某些特殊的符号只能匹配特殊的子expression式。 例如_Literal1,_Literal2,…只匹配文字值:

 addRule( new TARuleFromString( 'exp(_Literal1) * exp(_Literal2 )', 'exp( _Literal1 + _Literal2 )' ) ); 

这个规则将非文字expression式移动到左边:

 addRule( new TARuleFromString( '_Literal*_NonLiteral', '_NonLiteral*_Literal' ) ); 

任何以“_”开头的名字都是一个模式variables。 当系统匹配一个规则时,它保留了一堆已经匹配的符号的分配。

最后,不要忘记,规则可能会产生非终止replace序列。 因此,在减lessexpression的同时,记住这个过程,之前已经达到了哪个中间expression式。

在我的实现中,我不直接保存中间expression式。 我保留一个中间expression式的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' ) ); 

制定规则一stream的expression

有趣的一点是,由于上述规则是特殊expression式,通过expression式parsing器得到正确的评估,用户甚至可以添加新的规则,从而增强了应用程序的重写能力。

parsingexpression式(或更一般的:语言)

对于Cocoa / OBjC应用程序 , Dave DeLong的DDMathParser是语法分析mathexpression式的理想select。

对于其他语言,我们的老朋友Lex&Yacc或更新的GNU Bison可能会有所帮助。

更年轻,并且有一套准备使用的语法文件 , ANTLR是一个基于Java的现代parsing器生成器。 除纯粹的命令行使用外, ANTLRWorks还提供了一个GUI前端来构build和debugging基于ANTLR的parsing器。 ANTLR为各种主机语言 (如JAVA,C,Python,PHP或C#)生成语法。 ActionScript运行时目前被破坏 。

如果你想学习如何从下向上parsingexpression式 (或者一般的语言),我会提出这本由Niklaus Wirth (或德国书籍版 ),Pascal和Modula的着名发明者提供的免费书籍文本 -2。

这个任务可能变得相当复杂(除了最简单的转换之外)。 本质上这就是代数软件始终在做的事情。

你可以find一个可读的介绍如何完成(基于规则的评估),例如Mathematica 。

你想build立一个CAS(计算代数系统),话题是如此之广,有一个专门的研究领域。 这意味着有几本书可能会比SO更好地回答你的问题。

我知道一些系统构build的树会先减less常量,然后将树放到一个规范化的forms,然后使用一个已certificate/已知公式的大型数据库将问题转换成其他forms。

我相信你必须“蛮力”这样的树木。

你将不得不制定一些描述可能的简化的规则。 然后,你要穿过树,寻找适用的规则。 由于一些简化可能导致比其他简单的结果,你将不得不做一个类似的事情,你要做一个地铁计划最短的路线:尝试所有的可能性,并按照一些质量标准对结果进行分类。

由于这种场景的数量是有限的,您可以通过尝试运算符和variables的所有组合来自动发现简化规则,并再次使用遗传algorithmvalidation规则以前没有find,并且实际上简化了input。

乘法可以表示为加法,所以一个规则可能是a – a自行消除:2a-a = a + aa

另一个规则是首先将所有的分区放大,因为这些是分数。 例:

1/2 + 3/4发现所有的分割,然后将每个分数乘以所有其他分数两边的除数

4/8 + 6/8那么所有元素都有相同的除数,那么统一到(4 + 6)/ 8 = 10/8

最后你会发现顶部和底部5/4之间的最高公约数

应用到你的树上的策略将是从底部树叶向上工作,首先通过将其转换为附加来简化每个乘法。 然后简化每个添加像分数

所有的时候,你会再次检查你的快捷规则,以发现这样的简化。 要知道一个规则适用,你可能不得不尝试一个子树的所有排列。 例如,aa规则也适用于-a + a。 可能会有一个+ BA。

只是一些想法,希望给你一些想法…

一般来说你不能这样做,因为尽pipe它们在math上是一样的,但在计算机算术中可能不一样。 例如,-MAX_INT是未定义的,所以 – %a = / =%a。 类似的浮动,你必须适当地处理NaN和Inf。

我的天真的做法是有一些数据结构与每个function倒置(即dividemultiply )。 你显然需要进一步的逻辑来确保它们实际上是反向的,因为乘以3然后除以4实际上不是倒数。

虽然这是很原始的,但我认为这是一个很好的解决问题的办法,可以解决很多你在问题中提到的问题。

我期待着看到你的完整解决scheme,并敬畏于math的光辉:)

Interesting Posts