JEP(Java Expression Parser)学习笔记档

somewindy 贡献于2013-01-05

作者 lenovo  创建于2012-09-25 01:14:00   修改者lenovo  修改于2012-09-27 02:38:00字数21781

文档摘要:JEP基本用法入门错误处理计算表达式替代的解析和计算方法缩略的乘法入门在项目中使用JEP的类包是很简单的!下面的步骤将会使用很快入门。下载JEP包,下载格式为ZIP解压缩该包移动jep-x.x.x.jar到你选择的目录中重要:为了当编译你的程序时Java编译器能找到jep类,编译器需要知道jep的位置。所以你需要添加.jar文件的路径到你的CLASSPATH环境变量中。(如果你不清楚如何使用,请读);
关键词:

JEP(Java Expression Parser)学习笔记 By shenweifeng 2012-9-25 in HangZhou JEP 基本用法 1、 入门 2、 错误处理 3、 计算表达式 4、 替代的解析和计算方法 5、 缩略的乘法 入门 在项目中使用JEP的类包是很简单的!下面的步骤将会使用很快入门。 1、 下载JEP 包,下载格式为ZIP 2、 解压缩该包 3、 移动jep-x.x.x.jar到你选择的目录中 4、 重要:为了当编译你的程序时Java编译器能找到jep类,编译器需要知道jep的位置。所以你需要添加.jar文件的路径到你的CLASSPATH环境变量中。(如果你不清楚如何使用,请读这里);你的CLASSPATH变量应该包含某些东西如C:\java\packages\jep-x.x.x.jar,取决于你jar包放置的位置。或者,你也许需要在你的JAVA IDE中设置jar包的位置,使编译器能找到类库。 5、 在你的程序中,创建一个解析器对象,如: org.nfunk.jep.JEP myParser = new org.nfunk.jep.JEP(); 6、 添加标准函数和常量,如果你想有能力使用三角函数和pi,e计算表达式。 myParser.addStandardFunctions(); myParser.addStandardConstants(); 7、 通常情况下,你需要具体指明哪些变量能在表达式中被使用。如果一个变量在表达式被解析时还没被添加,解析器将声明表达式是不合法的(这种情况能被改变通过使用 允许未声明的变量)。 添加变量x并且初始化为0, myParser.addVariable("x", 0); 8、 解析表达式并计算它 myParser.parseExpression(ExpressionString); result = myParser.getValue(); 9、 变量值可以通过addVariable(String, double)改变 样本小应用程序提供了更多更广泛的表现来展示解析器的使用。 错误处理 错误可能发生在解析表达式过程中,也可能发生在计算表达式过程中。方法handError()将报告一个错误是否已经发生在最近的行为中(或解析,或计算)。如果返回值是true,你能使用getErrorInfo()方法获得已发生错误的进一步错误信息。 计算表达式 4个计算表达式的方法是可以获得的: l getValue(): 当你期望一个正常的浮点运算值时使用 l getComplexValue():当你期望一个复数时使用 l getValueAsObject():返回结果为一个对象(任意类型) l evaluate(Node root):从根节点计算一颗树(一种数据结构)并且返回一个对象。 前两种方法内在也算是getValueAsObject(),通过一些必须地转换就能变成double类型或者Complex类型。 替代的解析和计算方法 有一种替代方法解析和计算表达式,该方法为计算机应用提供了更多的灵活性,特别是在处理一系列相关的等式时。它们是: public Node parse(String expression) throws ParseException public Object evaluate(Node node) throws Exception 分别解析和计算表达式。这些能被使用如下: try { // Alternative syntax Node node1 = j.parse("z=i*pi"); //实际使用中需要打开允许赋值和使用未声明变量 j.evaluate(node1); Node node2 = j.parse("exp(z)"); Object val2 = j.evaluate(node2); System.out.println("Value: "+val2); } catch(ParseException e) { System.out.println("Error with parsing"); } catch(Exception e) { System.out.println("Error with evaluation"); } 因此,它更容易记录一系列等式。注意使用这种语法,异常需要被捕获而且getTopNode() 方法将不返回有意义的值。 缩略的乘法 你能使用缩略的乘法通过使用setImplicitMul(true)。 默认的设置是false(没有缩略乘法)。 缩略乘法允许表达式例如“2 x”能被解释为“2*x”。注意两个变量之间的空格是必须的使它们被解释为乘法。这同样适用于变脸后跟随一个数字。举个例子“y 3”被解释为“y*3”,但是y3被解释为单个名为y3的变量。如果一个数字位于变量之前,它们之间的空格是不需要的,缩略乘法照样有效。 变量 l 基础 l 允许未声明的变量 l 获得变量队列 l 赋值(Assignment) l 变量观察者 基础 变量有两种处理方式可供选择: 1、 不允许未声明的变量(默认):在这种情况下,解释一个表示式前添加表达式中的变量时必须的。添加通过使用addVariable()方法实现。 2、 允许未声明的方法:如果你允许未声明的变量,它们将在表达式被解析时自动地添加到符号表(symbol table)中。 在一个表达式已经被解析后,变量值可以通过使用addVaribale()或setVariable()方法来进行更新。这样就允许使用不同的值对表达式进行重复计算。 一个变量的值可以通过使用getVarValue()方法进行查询。一个更进一步的方法是使用getVar() ,返回一个代表该变量的对象。 这里的变量可能是常量,即不能改变值。添加一个新的常量使用addConstant,添加一个新的变量使用addVariable; 符号表类(SymbolTable)存储代表变量的对象而不仅仅是它们的值。该类的get和put已经过时,不赞成使用。这导致一个运算上的细微提高。 允许未声明的变量 使其能够解析未声明的变量,使用方法setAllowUndeclared(true)。 默认的设置是false(不允许未声明的变量)。 如果你不清楚声明变量名将会出现在将要解析的表达式中,你可以使用setAllowUndeclare(true)。设置该选择后,解析表达式前将变量添加到表达式中将不是必须的了。当解析时如果一个新变量被发现,它被自动的添加到符号表中。看获得变量队列,你可以读到有关怎么访问这些变量的方式。 获得变量队列 在未声明对象允许的情况下解析表达式,变量和常量队列被添加进解析器中,可以通过方法 getSymbolTable获得。该方法是最有效的当未声明变量选项被选中后。方法返回值是一个SymbolTable对象。注意SymbolTable继承HashTable。 赋值(Assignment) 赋值允许变量的值被设置通过使用等式中的“=”操作符,这样,执行操作例如x=3, Y=x^2,然后y将获得值9。赋值能被使用通过标准的JEP包,没有需要用到org.lsmp.djep树中的任何包。为了打开赋值功能,JEP的方法setAllowAssignment(true)需要调用。 // standard initialisation JEP j = new JEP(); j.addStandardConstants(); j.addStandardFunctions(); j.addComplex(); j.setAllowUndeclared(true); j.setImplicitMul(true); // switch assignment facilities on j.setAllowAssignment(true); // parse assignment equations j.parseExpression("x=3"); // evaluate it - no need to save the value returned j.getValueAsObject(); // parse a second equation j.parseExpression("y=2"); j.getValueAsObject(); // an equation involving above variables j.parseExpression("x^y"); Object val3 = j.getValueAsObject(); System.out.println("Value is " + val3); 变量观察者 符号表(Symbol)和变量(Variable)都实现了观察者模式。这允许对象被通知当一个新的变量被创建或者值被改变。这里有个使用的例子: public class MyObserver implements Observer { public void initialise() { SymbolTable st = j.getSymbolTable(); st.addObserver(this); st.addObserverToExistingVariables(this); } public void update(Observable arg0, Object arg1) { if(arg0 instanceof Variable) println("Var changed: " + arg0); else if(arg0 instanceof SymbolTable.StObservable) { println("New var: "+arg1); // This line is vital to ensure that // any new variable created will be observed. ((Variable) arg1).addObserver(this); } } } 数据类型 JEP支持大量的数据类型。这些包括数字(numbers),字符串(strings),矢量(vectors),复数(Complex numbers)。Numbers默认的表现方式是double类型。然而它也是可以改变表现形式的正如小节 使用自定义的number类。 Strings Strings能通过使用双引号加入到表达式中。它们能被连接通过使用“+”运算符,也能进行比较通过使用“==”和“!=”运算符。一个包含字符串的样例表达式是“foo”+“bar”==“foobar”,将被JEP计算为1(true); 添加字符串变量,使用方法addVariabe(String name,Object value)。如果一个表达式的结果是一个字符串,该值可以通过调用方法getValueAsObject()获得。 Vectors Vectors被要求为Double元素的一个集合。一个Vector是一系列的值,通过“,“分隔,使用方括号”[“,”]“包围。一个包含Vector的样例为: [3, 4, 5]*2 它将被JEP计算为[6,8,10]。添加一个vector为变量使用方法addVariable(String name,Object Value)。如果一个表达式的结果为一个vector,可以通过调用方法getValueAsObject()获得。 Complex numbers 如果你想在表达式中使用Complex numbers,你要在创建了解析器对象后调用addComplex()方法。这将添加一个虚数单位i。2个解析器函数re()和im()也被添加,可以使用它们获取复数的实数部分和虚数部分。你需要去导入org.nfunk.jep.type.Complex才能操作表达式中的复数。 通过使用虚数单位i,你可以操作表达式中的复数。一个简单的例子是(1+2*i)^3。当前 (re,im)标记是不支持的。 从表达式中获取一个复数,你需要使用getComplexValue()方法。它将进行运算并返回一个 Complex对象。注意这个类不仅实现了Complex numbers的运算,而且用来返回运算结果。 在运算前,通过使用addVariable(String name,double re,double im)可以添加一个复数变量或常量到解析器中。它包含三个参数:变量的名称,实数部分和虚数部分。 使用自定义的Number类 默认的当一个表达式例如“1+2“被解析,常量1和2在内部将会被解析为Double对象。在大多数情况下这是好的。但是在某些情况下,你也许想要使用自定义的类来作为表现形式。 这是可行的通过创建一个实现了NumberFactory借口的Number类。它包含了一个方法 createNumber(String value),该方法将返回一个初始化参数值的对象。你能加载你的自定义number factory使用JEP构造器JEP(boolean traverse_in, Boolean allowUndeclared_in, Boolean implicitMul_in, NumberFactory numberFactory_in)。 自定义的对象需要操作通过自定义的函数,你可以创建参考下面的使用说明,位于 自定义函数页面。 自定义的类型 在大多数情况下,你只需要使用JEP提供的已存在的少量的类型(Double,Complex,Vector,String)。但是假如你需要计算包含其他类型的表达式。这是可行的通过使用addVariable(String name,Object value)去添加任意类型的变量。一个变量类型唯一有关的地方在函数类中。 当一个表达式被计算,变量的值被类中的方法操作。这些包括操作(加法和减法),同样还有函数Sine和Cosine。不修改源码的情况下,只有默认类型能被这些类操作。所以,为了有能力操作自己的类型,你需要去修改这些类,或者编写你自己的函数类依照在自定义函数小节中描述的。 运算符 内嵌的运算符 所有常用的算术操作都被支持。Boolean操作也都被支持。Boolean表达式计算后为1或0(分别代表真和假)。 符号表示该运算符能被指定对象的使用。关于运算符优先级可以参考语法部分获取详细信息。   Double Complex String Vector Power ^     Boolean Not !       Unary Plus, Unary Minus +x, -x     Modulus %       Division /   Multiplication *   Addition, Subtraction +, - (only +)   Less or Equal, More or Equal <=, >=       Less Than, Greater Than <, >       Not Equal, Equal !=, ==   Boolean And &&       Boolean Or ||   新增运算符 当前增加运算法的唯一方法就是修改JEP和源码。为一个解析器添加运算符一下几步是需要的: n 创建一个新的标记,含描述名称,说明,一个代表该操作的字符串。 n 修改语法规则,包含一个新运算符的规则。 n 创建一个新的对象实现了org.nfunk.jep.function.PostfixMathCommandI接口,提供操作运算符的方法。 n 修改或替换org.nfunk.jep.OperatorSet绑定解析器中的运算符到PostFixMathCommand 在尝试添加一个运算符之前,你应该熟悉如何添加一个函数到JEP中。 JavaCC The Parse generator 解析器通过使用JavaCC解析/扫描 生成器创建 而不是通过手写的代码。生成器读取文件Parser.jjt(使用特殊的语言编写,定义了语法),生成Parser.java和其他一些实现Parser的类文件。 在尝试修改解析器之前,你需要读一些关于JavaCC和JJTree的文档 。 生成解析器有三个过程: 1、 JJTree处于运行状态。它读取文件Parser.jjt,然后创建一个文件Parser.jj。这一步的目的是为了添加需要的代码用来操作抽象语法树(树的结构代表了一个数学表达式)。创建parser.jj的同时也创建了一些java文件:Node.java代表解析器中的基础节点。ASTConstant.java一个代表常量“0”或“1”的节点;ASTVarNode。Java一个代表变量的节点。 2、 Javacc处于运行状态。它读取Parser.jj并且生成Parser.java,真正的实现Parser 接口的代码。Parser.java是一个非常复杂的文件。几乎包含了2000行代码。 3、 正常的java编译步骤编译Parser.java 当项目通过使用build.xml文件构建时这个过程应该被自动的进行。简单的再次编译所有的java文件时不够的。 只有Parser.jjt文件可以被修改,Parser.jj,Parser.java不应该被修改,因为他们将被复写在构建过程中。更深一步,ASTConstant.java, ASTFunNode.java, ASTStart.java, ASTVarNode.java, JavaCharStream.java, JJTParserState.java, Node.java, SimpleNode.java, ParseException.java, ParserConstants.java, ParserTokenManager.java, ParseTreeConstants.java, Token.java, TokenMgrError.java也不应该被修改因为它们也是自动生成的。 函数 注意你总是能添加新的自定义函数。下面的每一个函数都能被运用到打的对象中。 函数的添加使用方法addStandardFuncions();     Double Complex String Vector Sine sin(x)     Cosine cos(x)     Tangent tan(x)     Arc Sine asin(x)     Arc Cosine acos(x)     Arc Tangent atan(x)     Arc Tangent (with 2 parameters) atan2(y, x)       Hyperbolic Sine sinh(x)     Hyperbolic Cosine cosh(x)     Hyperbolic Tangent tanh(x)     Inverse Hyperbolic Sine asinh(x)     Inverse Hyperbolic Cosine acosh(x)     Inverse Hyperbolic Tangent atanh(x)     Natural Logarithm ln(x)     Logarithm base 10 log(x)     Exponential (e^x) exp(x)     Absolute Value / Magnitude abs(x)     Random number (between 0 and 1) rand()         Modulus mod(x,y) = x % y       Square Root sqrt(x)     Sum sum(x,y,z)   If if(cond,trueval,falseval)       Str (number to string) str(x) Binomial coefficients binom(n,i) Integer values 函数添加使用addComplex():     Double Complex String Vector Real Component re(c)     Imaginary Component im(c)     Complex Modulus (Absolute Value) cmod(c)     Argument (Angle of complex value, in radians) arg(c)     Complex conjugate conj(c)     Complex, constructs a complex number from real and imaginar parts complex(x,y)       Polar, constructs a complex number from modulus and argument polar(r,theta)       自定义函数 下面是一个自定义函数被添加的例子。 假定你想要添加一个函数“half”用来取一个值的一半(演示目的)。 1、 创建一个继承PosfixMathCommand(位于org.nfunk.jep.function包中)的类。在这个例子中,我们给它取名“Half”. 2、 在构造器中设置获取的参数的数量。在我们的例子中,个数为1.完成这个输入“numberOfParameters = 1;“如果你想要允许任意个数的参数,初始化numberOfParameters为-1。我们强烈建议你学习Sum.java,将它作为一个获取任意个参数的例子。 3、 实现方法run(Stack inStack).参数通过栈对象(Stack)传递。这同一个栈也被用来作为函数的输出。所以我们首先需要弹出战中的参数,计算结果,最终将结果压入到栈中。 1. public void run(Stack inStack) throws ParseException { 2. 3. // check the stack 4. checkStack(inStack); 5. 6. // get the parameter from the stack 7. Object param = inStack.pop(); 8. 9. // check whether the argument is of the right type if (param instanceof Double) { 10. // calculate the result double r = ((Double)param).doubleValue() / 2; 11. // push the result on the inStack inStack.push(new Double(r)); } else { throw new ParseException("Invalid parameter type"); 12. } 13. } 4、 在你的主程序或应用中使用解析器,添加新的函数。通过输入如下代码: parser.addFunction("half", new Half()); 5、 如果参数numberOfParameters == -1,你可能希望复写方法boolean checkNumberOfParameters(int n)来捕获不合法的参数数量。 源文件 l CustFunc.java(一个测试类) l Half.java(Half函数类) 高级特性 操作表达式树 方法getTopNode()被用来获取解析后的表达式树。这是非常有用的如果你不仅仅想要计算表达式的值。举个例子,你也许想要确定一个表达式的衍生物。为了实现这个目的,你需要直接访问表达式树。 表达式树由节点组成。解析树的每一个节点都是一个下面类型的对象: l ASTFunNode – 函数和运算符 l ASTConstant – 常量 l ASTVarNode – 变量 它们都继承类SimpleNode(该类实现了Node接口)。二元运算符(+,-,*,…)和函数式ASTFunNodes。运算符类型(函数类)被存储在pfmc成员中,作为一个字符串在name成员中。使用getPFMC()和getName()方法来访问这些成员。 遍历表达式树,你可以使用visitor类(ParserDumpVistor是以一个例子,用来大爷所有的节点)。查看EvaluatorVisitor类可以学习表达式是如何通过访问者模式倍计算的。 语法 语法是私用JavaCC的jjdoc工具操作Parser.jj生成的。运算符优先级是从低到高的排序的(从上到下)。 Start ::= ( Expression ( | ) | ( | ) ) Expression ::= AssignExpression | OrExpression AssignExpression ::= ( Variable Expression ) OrExpression ::= AndExpression ( ( AndExpression ) )* AndExpression ::= EqualExpression ( ( EqualExpression ) )* EqualExpression ::= RelationalExpression ( ( RelationalExpression ) | ( RelationalExpression ) )* RelationalExpression ::= AdditiveExpression ( ( AdditiveExpression ) | ( AdditiveExpression ) | ( AdditiveExpression ) | ( AdditiveExpression ) )* AdditiveExpression ::= MultiplicativeExpression ( ( MultiplicativeExpression ) | ( MultiplicativeExpression ) )* MultiplicativeExpression ::= UnaryExpression ( ( PowerExpression ) | ( UnaryExpression ) | ( UnaryExpression ) | ( UnaryExpression ) | (
UnaryExpression ) | ( UnaryExpression ) )* UnaryExpression ::= ( UnaryExpression ) | ( UnaryExpression ) | ( UnaryExpression ) | PowerExpression PowerExpression ::= UnaryExpressionNotPlusMinus ( ( UnaryExpression ) )? UnaryExpressionNotPlusMinus ::= AnyConstant | ( Function | Variable ) | Expression | ListExpression ListExpression ::= ( Expression ( Expression )* ) Variable ::= ( Identifier ) Function ::= ( Identifier ArgumentList ) ArgumentList ::= ( Expression ( Expression )* )? Identifier ::= ( | ) AnyConstant ::= ( | RealConstant ) RealConstant ::= ( | ) FAQ(常见问题) Q:我注意到JEP对一个明确的表达式没有给出一个精确的结果。为什么会这样?有什么我能做的吗? A:你将注意到当你计算例如简单的运算“8-7.9”,结果将被0.09999999999999964而不是0.1。这个不精确性是浮点运算的结果。在内部JEP对数字默认使用双精度Double类型。不幸的,甚至对微不足道的算式“8-7.9”,计算器不能很好地正确表示。 你将注意到相同的如果你运行一个java程序。 double a=8, b=7.9; System.out.println("a-b = " + (a-b)); 尽管浮点数运算在大部分应用中都是足够精确的,这些错误不应该被忽略当在一些精确性至关重要的地方。 DJEP类库提供了工具表现精确地运算,使用更多的精密数字类型。DJEP包含GroupJep,用来取代精确的计算。查看Groups文档获取更详细的信息。它使用大整数类和大小数类表示数字类型而不是采用double类型。 Q:我不想要JEP提供的所有函数。有没有方法加载它们中的一些,或者完全移除包中的一些? A:缩减JEP为一个精简的运算器。 Option 1: 在一些情况下,你不需要JEP支持的所有内置函数。最简单的方法是解析前不要调用addStandardFunctions()方法。然后你调用addFunction()添加你需要的特定的函数。 Option 2: 如果你认为减小JEP到最小尺寸是有必要的,你可以移除所有函数类除了运算符(如果你不需要它们)。这将是你只拥有一个可以做简单计算的解析器,而没有特别的函数例如sin()和cos()。这将节约你17kb的类(占JEP得15%)。 指导说明: - 函数类都位于org.nfunk.jep.function包中。运算符(+,-,*,/,…)也都作为函数实现。这些不应该被移除如果你想JEP正常工作的话。下面的函数类被用来实现运算符: - Add Modulus Substract - Comparative Multiply UMinus - Divide Not - Logical Power - 打开JEP的源码包,找到addStandardFunctions方法。它包含了几乎所有非运算符函数的列表。对于每一个你从函数目录移除的函数类,你也必须移除该方法中相关联的行。 - 重新编译JEP,你就有了一个精简的运算器。 Extensions 扩展—JEP中的微分,矢量,矩阵 JEP扩展部分提供了一些添加的特点到JEP包中: l 打印:等式能被简单的打印或者转换成字符串,智能地使用方括号。 l 简化(合并同类项):等式如2.0*x^1.0*1.0+0.0能被简化为2.0*x l 微分:等式能被微分,这些能被程序性的指定或者等式本身指定。 l 矢量和矩阵:充分支持矢量和矩阵操作。 l 操作解析树的工具:一些工具可以获取到,使用其进行解析树的创建,复制和操作。 l 组:精确的计算,包括整数和其他一些组。 l 快速运算:计算器的普通计算速度为2倍。最快可以达到10倍。 这些功能类被提供在包org.lsmp.djep树中。 Feature Package (JavaDoc) Printing org.lsmp.djep.xjep.* Simplification org.lsmp.djep.xjep.*, and org.lsmp.djep.sjep.* re-entrant parser org.lsmp.djep.xjep.* Variables with equations org.lsmp.djep.xjep.* Macro Functions (functions defined by an equation) org.lsmp.djep.xjep.* Utilities for examining and modifying the parse trees org.lsmp.djep.xjep.* Differentiation org.lsmp.djep.djep.* (1) Basic Vectors and Matrices org.lsmp.djep.vectorJep.* Advanced Vectors and Matrices org.lsmp.djep.matrixJep.* (2) Calculation over arbitrary groups including exact arithmetic over integers and rationals. org.lsmp.djep.groupJep.* Fast evaluation. Single valued org.lsmp.djep.rpe.* Matrix and vectors org.lsmp.djep.mrpe.* Notes: (1) requires the org.lsmp.djep.xjep.* package. (2) requires the org.lsmp.djep.xjep.*, org.lsmp.djep.djep.* org.lsmp.djep.vectorJep.* packages. 举例说明 一些例子可以从包org.lsmp.djepExamples.*下找到。例子被分为两类。AssignmentExample, XJepExample, PrintExample, DiffExample, VectorExample, VectorPrint, and MatrixSpeed是简单应用,提供了不同功能特色的例子。另一种类型是控制台应用程序,允许输入等式,有DJepConsole, VectorConsole, MatrixConsole and GroupConsole。 网页 · DJep home page · Example applets JEP, XJep, DJep, VectorJep, MatrixJep, GroupJep. · JEP home page · Sourceforge project page includes a help forum · Javadoc Xjep –Jep的扩展 包org.lsmp.djep.xjep提供了一些对标准JEP包的扩展: l 打印:等式能被简单的打印或者转换成字符串,智能地使用方括号。 l 简化(合并同类项):等式如2.0*x^1.0*1.0+0.0能被简化为2.0*x l Re-entrant解析器:允许分割等式。 l 变量伴随等式和懒惰计算。 l 宏函数:函数通过简单的函数调用指定。 l 新函数包括Sum(x^2,x,1,10), l 操作解析树的工具:一些工具可以获取到,使用其进行解析树的创建,复制和操作。 一个交互式控制台小应用阐述了XJep的功能。 基本用法 为了使用当前页提到的功能,标准的JEP类需要使用org.lsmp.djep.xjep.XJep。所有JEP的标准方法都是可以获得的。举个例子: import org.nfunk.jep.*; import org.lsmp.djep.xjep.*; public class XJepExample { public static void main(String[] args) { XJep j = new XJep(); j.addStandardConstants(); j.addStandardFunctions(); j.addComplex(); j.setAllowUndeclared(true); j.setImplicitMul(true); j.setAllowAssignment(true); try { Node node = j.parse("x = 3"); Node processed = j.preprocess(node); Node simp = j.simplify(processed); Object value = j.evaluate(simp); System.out.println(value.toString()); j.println(simp); } catch (ParseException e) {} catch (Exception e) {} } 队列parse,preprocess,simplify,evaluate是使用这个包的惯用方法。 打印和转换成字符串 一些不同的方法可以获得用来打印等式: public void print(Node node); //打印表达式树 //特定节点标准输出 public void print(Node node,PrintStream out); //输出到指定的流 public void println(Node node); // 结尾换行 public void println(Node node,PrintStream out); // 结尾换行 public String toString(Node node); // 返回一个字符串 这些方法转换被节点具体指明的表达式到一行字符串。方法尝试产生一个尽可能简单的表达式字符串。然而括号被模棱两可的用来解析。因此等式“a+(b*c)”将被打印为a+b*c而a*(b+c)将被解析为a*(b+c)。 XJep j = new XJep(); .... try { // parse expression Node node = j.parse("a*b+c*(d+sin(x))"); // print it j.println(node); // convert to string String str = j.toString(node); System.out.println("String is '"+str+"'"); } catch(ParseException e) { System.out.println("Parse error"); } 默认情况下,不是必须的括号将被移除。你想要打印大量的空格(举例准确测试表达式被如何解析),你可以使用: j.getPrintVisitor().setMode(PrintVisitor.FULL_BRACKET,true); j.println(node); 在将来的一些场合打印工具如产生MATHML或其他输出格式将被包括进来。 通过提供一个java.text.NumberFormat对象,数字的展示方式可以被设置。 NumberFormat format = NumberFormat.getInstance(); j.getPrintVisitor().setNumberFormat(format); format.setMaximumFractionDigits(3); format.setMinimumFractionDigits(0); String s1 = "[10,0,0.1,0.11,0.111,0.1111,0.999,0.9999]"; Node n1 = j.parse(s1); System.out.println(j.toString(n1)); 打印[10,0,0.1,0.11,0.111,0.111,0.999,1],换言之,数字被展示为四舍五入的3位小数。 简化 Xjep类也提供了常规的程序来简化表达式: XJep j = new XJep(); .... Node node=j.parse("1*x^1+0"); j.println(node); Node simp=j.simplify(node); j.println(simp); 代码输出的结果是: 1.0*x^1.0+0.0 x 注意到等式的多余部分像加0,乘1,一次方等从等式中移除。所有的常量表达式如1+2*3*cos(0)也将被简化(在这种情况下为7)。简化运算法则是不完美的,可能存在一些表达式不能被完全简化。我们希望在未来的日子里改进算法。 根据实验一个更先进的简化程序被提供(位于包org.lsmp.djep.sjep.*中)。这个转换节点为一个多项式,保证相同类型的单项式被添加到一起。也允许完全展开多项式。它也应该操作容纳其它函数的函数。 根据实验org.lsmp.djep.rewrite.*允许随意的重写等式。开发非常早的时期。 Re-entrant解析器 一个新的语法规则通过使用分号“;”来分割表达式。这将允许字符串如"x=1; y=2; z=x+y;"被解析。为了能使用,XJep的re-entrant方法应该被使用访问解析器。 public void restartParser(String string); public void restartParser(Reader reader); public Node continueParsing() throws ParseException; 前两个方法重新初始化解析器,命令它从字符串或者字符流(允许一个队列的等式被定义在文件中)中读取等式。最后一个方法读取一个等式,完成当遇到一个分号。当没有数据可以读取时返回一个null值。举个例子: XJep j = new XJep(); j.setAllowAssignment(true); ... j.restartParser("x=1; y=2; z=x+y;"); try { Node node; while((node = j.continueParsing()) != null) { Node simp = j.simplify(j.preprocess(node)); Object value = j.evaluate(simp); j.println(simp); System.out.println(value.toString); } } catch(Exception e) {} 注意:如果遇到空等式,null也将被返回。对字符串"x=1; ;y=2; z=x+y;"循环将结束在解析y=2之前 变量和预处理场所 内在的在包org.lsmp.djep.xjep中值和等式都在变量中。一个变量的等式被设置通过使用赋值语法“x=3”或者“y=x^2”。然而,等式不是被解析器设置的。代替的一个新的预处理方法在解析之后,计算之前被调用。    Node node = j.parse("x=3");     Node processed = j.preprocess(node);  // sets the equation for variable x     Node simp = j.simplify(processed);     Object value = j.evaluate(simp);     Node node2 = j.parse("y=x^2");     Node processed2 = j.preprocess(node2); // sets the equation for variable y     Node simp2 = j.simplify(processed2);     Object value2 = j.evaluate(simp2); 变量的等式可以被恢复通过使用 j.getVar("y").getEquation(); 在Djep或MatrixJep使用情况下,预处理方法(preprocess)有额外的功能。 重复使用同一套等式 同一套等式可以被变量的不同值重复使用。然而有一点要注意的是需要保持所有变量当前值。 有一些不同的策略可能被用到。 重新计算(Re-evaluation:):通过每个节点调用j.evaluate,表达式的值将会被赋值给等号左边的元素。按照正确的顺序完成是很重要的, 等式可以设置计算变量的值,使以后需要用到该等式的时候可以使用。 XJep j = new XJep(); ... // Setting up equations x=3; y=x^2; z=y+x; Node node1 = j.preprocess(j.parse("x=3")); System.out.println(j.evaluate(node1)); // prints 3 Node node2 = j.preprocess(j.parse("y=x^2")); System.out.println(j.evaluate(node2)); // prints 9 Node node3 = j.simplify(j.preprocess(j.parse("z=y+x"))); System.out.println(j.evaluate(node3)); // prints 12 // Change value of x, evaluate equations in turn j.setVarValue("x",new Double(4)); System.out.println(j.evaluate(node2)); // prints 16 System.out.println(j.evaluate(node3)); // prints 20 System.out.println("z: "+j.getVarValue("z").toString()); // prints 20 从它们的等式中计算变量的值:方法calcVarValue重新计算使用的变量的值。注意预处理调用常被用来设置等式中变量的值,这是很重要的。变量的值应该按顺序被计算。 j.setVarValue("x",new Double(5)); System.out.println(j.calcVarValue("y").toString()); // prints 25 System.out.println(j.calcVarValue("z").toString()); // prints 30 懒惰计算(Lazy evaluation):懒惰计算策略被计算器用来计算变量的值。每一个变量都有一个标志位(flag)来判定该值是最新的还是合法的。如果合法,当前值将在等式中被使用,如果不合法,变量的等式被用来计算变量的值。这种计算将以递归的方式产生,如z依赖于y,y依赖于x,然后x的等式将被第一次执行。 应该注意无论什么时候它们的等式被计算,变量的值时都被标记合法的。在很长的等式链中,这可能引起古怪的行为。方法getSymbolTable().clearValues()被调用可以标记所有的变量为不合法的(除了常量),因此保证所有中间级的等式将在需要的时候被执行。该方法应该在等式的值被通过setVarValue设置之前调用。 上面的结果是如果clearValues被调用就没有必要去计算中间等式,只要计算最终变量或者需要被计算的等式。 j.getSymbolTable().clearValues(); j.setVarValue("x",new Double(6)); System.out.println(j.findVarValue("z").toString()); // prints 42 j.getSymbolTable().clearValues(); j.setVarValue("x",new Double(7)); System.out.println(j.evaluate(node3)); 这个方案的动机是如果有区别,部分衍生的变量被自动计算 将会起作用。 总结变量的使用 XJep包中变量使用的总结 Class Method Action JEP public void addConstant(String name,Object value) Adds a constant variable whose value can not be changed. JEP public void addVariable(String name,Object value) Adds a mutable variable. JEP public boolean setVarValue(String name,Object value) Sets the value of a mutable variable. False on error. JEP public Variable getVar(String name) Returns the object representing the variable. JEP public Object getVarValue(String name) Gets the value of the variable. Does not re-calculate. JEP public SymbolTable getSymbolTable() Returns the symbol table containing all the variables. XJep public Object calcVarValue(String name) Calculates the value of a variable from its equation. XJep public preprocess(Node node) Causes the equations of variable on the lhs of an assignment equation to be set. XVariable public Node getEquation() Returns the equation of a variable. XVariable public Object calcValue() Calculates the value of a variable from its equation. SymbolTable public void clearValues() Marks all non constant variables as invalid. 还有更多操作变量的方法位于类Variable, XVariable, SymbolTable and XSymbolTable中。标准的HashTable方法也能被使用。 宏函数 XJep也使你在自己的代码中自定义函数变得更简单,不需要再去创建PostFixMathCommand的子类。这样的函数可以通过使用包含定义的等式的字符串定义。 // creates a function with 1 argument j.addFunction("zap",new MacroFunction("zap",1,"x*(x-1)/2",j)); Node node = j.parse("zap(10)"); System.out.println(j.evaluate(node)); // print 45 查看MacroFunction了解更详细的语法细节。现在只支持一维,不支持矢量和矩阵。 函数SumType 函数SumTpe提供了一个很有意义逗号标识的累加。举个例子:Sum(x^2,x,1,10) 计算x从1到1运算的x^2的值,如1^2+2^2+…+10^2。另外一些sum_type函数包括: l Sum – 计算范围内的值的总和。 Sum(x^2,x,1,10,2) calculates the 1^2+3^2+5^2+7^2+9^2 i.e. in steps of 2. l Product – 计算结果 A product function product(x^2,x,1,10) finds the product of x^2 with x running from 1 to 10. l Min – 找到最小值 l Max – 找到最大值 l MinArg – 找到最小值的序号 l MaxArg – 找到最大值的序号 l Simpson – 近似计算积分 注意到所有函数都以大写字母开头用来区分sum(1,2,3,4),只用来计算参数总和。 其它函数 函数toHex()转换十进制为十六进制,toHex(255)àoxff。如果第二参数被给出,该参数指定了显示的十六进制的小数位数。 函数toBase(val,base)和toBase(val,base,digits)转换成给定小数位数的以base为基数的进制。 找到等式中的变量 函数getVarsInEquation可以找到所有函数中的变量。函数getVArsInEquation找到等式中的变量。如果这些变量中的任意一个被等式定义,也返回这些变量。一个有序队列被返回,从而依次计算每一个变量将返回正确的值。 其他工具 一些其他的功能也被包括在xjep包中。这些全部操作代表等式的生成树。 · DeepCopyVisitor:创建一颗节点树的备份。 · SubstitutionVisitor:用一个表达式代替另一个变量。 · NodeFactory: 这个方法包含了构建节点树的方法。通过重复调用该方法可以构建一个节点树。 · TreeUtils: 大量的工具用来确认节点的类型和访问节点的属性。 · XOperatorSet:一个运算符的集合包含了很多的信息如优先级、交换性等。 DJep --- JEP中等式的微分 包org.lsmp.djep.djep中的DJep类提供了微分的功能。DJep类应该代替JEP或XJep的使用,因为它拥有两个类的全部功能。 用法 微分可以通过两种方式使用: 1、 使用DJep的differentiate(Node node,String name)方法。 2、 在等式中使用”diff”运算符 下面的代码给出了一个使用的例子: import org.nfunk.jep.*; import org.lsmp.djep.xjep.*; public class DiffExample { public static void main(String[] args) { /* initialisation */ DJep j = new DJep(); j.addStandardConstants(); j.addStandardFunctions(); j.addComplex(); j.setAllowUndeclared(true); j.setAllowAssignment(true); j.setImplicitMul(true); // Sets up standard rules for differentiating sin(x) etc. j.dv.addStandardDiffRules(); try { // parse the string Node node = j.parse("sin(x^2)"); // differentiate wrt x Node diff = j.differentiate(node,"x"); j.println(diff); // simplify Node simp = j.simplify(diff); // print j.println(simp); // This time the differentiation is specified by // the diff(eqn,var) function Node node2 = j.parse("diff(cos(x^3),x)"); // To actually make diff do its work the // equation needs to be preprocessed Node processed = j.preprocess(node2); j.println(processed); // finally simplify Node simp2 = j.simplify(processed); j.println(simp2); } catch(ParseException e) { System.out.println("Error with parsing"); } } 注意到微分后进行表达式的简化是很有必要的。因为运算器重复调用sum,prduct,quotient,chain规则。因此衍生物“x^2”将被微分为“2*x^1”,而该表达式可以被简化为“2*x”。 也注意到如果diff(eqn,var)运算符被使用时preprocess必须被调用。这个方法将通过扫描寻找微分的地方,当它遇到一个,它将根据第二个参数微分第一个参数,第二个参数必须为变量。举个例子:过程将转换“diff(x^3,x)”为“3*x^2”。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享文档获得金币 ] 1 人已下载

下载文档