【java表达式引擎】三、轻量级表达式引擎Expr4j
expr4j,轻量级表达式引擎Expr4j
expr4j学习
expr4j学习路线
一、介绍
exp4j 能够评估实域中的表达式和函数。这是一个小型 (40KB) 库,没有任何外部依赖项,实现了Dijkstra 的 Shutting Yard Algorithm。exp4j 带有一组标准的内置函数和运算符。此外,用户可以创建自定义运算符和函数。
exp4j 能够支持excel中的十几种数据函数和最基础的运算符号,并且用户可以自定义函数和运算符,扩展也比较方便容易,只不过都是使用的double最为的基础数据类型,如果精度特别高的,可以自己可以把源码下载下来,然后对项目进行修改。
官网地址:https://www.objecthunter.net/exp4j/
github: https://github.com/fasseg/exp4j
二、坐标依赖
1、依赖
<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
<version>0.4.8</version>
</dependency>
2、最全阿里镜像
大家在依赖坐标的时候可能会下载不下来,推荐配置上阿里的所有镜像仓库,可以再阿里仓库官网查看
https://developer.aliyun.com/mvn/guide
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>D:/Maven/repository</localRepository>
<mirrors>
<mirror>
<id>aliyun-central</id>
<mirrorOf>central</mirrorOf>
<name>aliyun central</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
<mirror>
<id>aliyun-public</id>
<mirrorOf>public</mirrorOf>
<name>aliyun public</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>aliyun-public</id>
<mirrorOf>jcenter</mirrorOf>
<name>aliyun jcenter</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
<mirror>
<id>aliyun-spring</id>
<mirrorOf>aliyun-spring</mirrorOf>
<name>aliyun spring</name>
<url>https://maven.aliyun.com/repository/spring</url>
</mirror>
<mirror>
<id>aliyun-spring-plugin</id>
<mirrorOf>aliyun-spring-plugin</mirrorOf>
<name>aliyun spring-plugin</name>
<url>https://maven.aliyun.com/repository/spring-plugin</url>
</mirror>
<mirror>
<id>aliyun-apache-snapshots</id>
<mirrorOf>aliyun-apache-snapshots</mirrorOf>
<name>aliyun apache-snapshots</name>
<url>https://maven.aliyun.com/repository/apache-snapshots</url>
</mirror>
<mirror>
<id>aliyun-google</id>
<mirrorOf>aliyun-google</mirrorOf>
<name>aliyun google</name>
<url>https://maven.aliyun.com/repository/google</url>
</mirror>
<mirror>
<id>aliyun-gradle-plugin</id>
<mirrorOf>aliyun-gradle-plugi</mirrorOf>
<name>aliyun gradle-plugin</name>
<url>https://maven.aliyun.com/repository/gradle-plugin</url>
</mirror>
<mirror>
<id>aliyun-jcenter</id>
<mirrorOf>aliyun-jcenter</mirrorOf>
<name>aliyun jcenter</name>
<url>https://maven.aliyun.com/repository/jcenter</url>
</mirror>
<mirror>
<id>aliyun-releases</id>
<mirrorOf>aliyun-releases</mirrorOf>
<name>aliyun releases</name>
<url>https://maven.aliyun.com/repository/releases</url>
</mirror>
<mirror>
<id>aliyun-snapshots</id>
<mirrorOf>aliyun-snapshots</mirrorOf>
<name>aliyun snapshots</name>
<url>https://maven.aliyun.com/repository/snapshots</url>
</mirror>
<mirror>
<id>aliyun-grails-core</id>
<mirrorOf>aliyun-grails-core</mirrorOf>
<name>aliyun grails-core</name>
<url>https://maven.aliyun.com/repository/grails-core</url>
</mirror>
<mirror>
<id>aliyun-mapr-public</id>
<mirrorOf>aliyun-mapr-public</mirrorOf>
<name>aliyun mapr-public</name>
<url>https://maven.aliyun.com/repository/mapr-public</url>
</mirror>
</mirrors>
<profiles>
</profiles>
</settings>
三、exp4j 支持丰富的表达式
## 1、评估表达式
为了评估一个表达式。ExpressionBuilder
类可用于创建能够评估的Expression对象
。可以通过调用ExpressionBuilder.function()
和ExpressionBuilder.operator()
来设置自定义函数和自定义运算符。任何给定的变量都可以通过调用Expression.variable()在 ExpressionBuilder.build( ``)返回的``Expression
对象上设置
/**
* SIN函数 一秒计算正弦值
*/
@Test
public void test3() {
Expression e = new ExpressionBuilder("3 * sin(y) - 2 / (x - 2)")
.variables("x", "y")
.build()
.setVariable("x", 2.3)
.setVariable("y", 3.14);
double result = e.evaluate();
System.out.println(result);//-6.66188870791721
}
2、异步计算表达式
/**
* 异步计算表达式,巧用LOG函数 按指定底数返回对数
LOG(number,base)
Number 为用于计算对数的正实数。
base 为对数的底数。如果省略底数,则假定其值为10。
*/
@Test
public void test4() throws ExecutionException, InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(1);
Expression e = new ExpressionBuilder("3log(y)/(x+1)")
.variables("x", "y")
.build().setVariable("x", 2.3).setVariable("y", 3.14);
Future<Double> future = e.evaluateAsync(exec);
double result = future.get();
System.out.println(result);//1.0402025453819654
}
3、变量声明
变量名必须以字母或下划线_
开头,并且只能包含字母、数字或下划线。
以下是有效的变量名:
-
varX
-
_x1
-
_var_X_1
而
1_var_x
不是因为它不以字母或下划线开头。
4、隐式乘法
因此,像2cos(yx)这样的表达式将被解释为
2max(yx)
/**
*,像2cos(yx)这样的表达式将被解释为2*cos(y*x)
*/
@Test
public void test5() throws ExecutionException, InterruptedException {
double result = new ExpressionBuilder("2cos(xy)")
.variables("x","y") .build
()
.setVariable("x", 0.5d)
.setVariable("y", 0.25d)
.evaluate ();
System.out.println(result);//1.984395334458658
}
5、数值常数
自 0.4.6 版起,以下常用常数已添加到 exp4j 并自动绑定:pi, π Math.PI 中定义的 π 值,e欧拉数 e 的值,φ黄金比例 (1.61803398874) 的值)
String expr = "pi+π+e+φ";
double expected = 2*Math.PI + Math.E + 1.61803398874d;
Expression e = new ExpressionBuilder(expr).build();
assertEquals(expected, e.evaluate(),0d);
6、科学计数法
从 0.3.5 版开始,可以使用科学记数法来表示数字,请参阅 wikipedia。该数字分为有效数/尾数y和``yEx
形式的指数x
,计算为y * 10^x
。请注意,‘e/E’ 不是运算符,而是数字的一部分,因此无法评估像1.1e-(x*2)这样的表达式。
使用精细结构常数α=7.2973525698 * 10^−3
的示例:
//科学计数法
@Test
public void test7() {
String expr = "7.2973525698e-3";
double expected = Double.parseDouble(expr);
Expression e = new ExpressionBuilder(expr).build();
assertEquals(expected, e.evaluate(),0d);
System.out.println(expected);//0.0072973525698
}
7、自定义函数
您可以扩展抽象类 Function 以便在表达式中使用自定义函数。你只需要实现apply(double... args)
方法。在以下示例中,创建了一个以 2 为底的对数函数,并在以下表达式中使用。
使用恒等式计算任意底的对数的自定义函数log(value, base) = ln(value)/ln(base)
//自定义函数 logb函数 按指定底数返回对数
@Test
public void test8() {
Function logb = new Function("logb", 2) {
@Override
public double apply(double... args) {
return Math.log(args[0]) / Math.log(args[1]);
}
};
double result = new ExpressionBuilder("logb(8, 2)")
.function(logb)
.build()
.evaluate();
double expected = 3;
assertEquals(expected, result, 0d);
System.out.println(expected);//3.0
}
8、自定义平均函数:
//自定义4个平均函数 avg函数
@Test
public void test9() {
Function avg = new Function("avg", 4) {
@Override
public double apply(double... args) {
double sum = 0;
for(double arg : args) {
sum += arg;
}
return sum / args.length;
}
};
double result = new ExpressionBuilder("avg(1,2,3,4)").function(avg).build().evaluate();
System.out.println(result);//2.5
double expected = 2.5d;
assertEquals(expected, result, 0d);
}
9、自定义运算符
您可以扩展抽象类Operator
以声明用于表达式的自定义运算符,符号是由!,#,§,$,&,;,:,~,<,>,|,= 组成的字符串。
. 请注意,添加带有已使用符号的运算符会覆盖任何现有运算符,包括内置运算符。因此可以覆盖例如+
运算符。Operator 的构造函数最多接受 4 个参数:
-
用于此操作的符号(由
!,#,§,$,&,;,:,~,<,>,|,=
组成的字符串) -
如果操作是左关联的
-
操作的优先级
-
运算符的操作数(
1 或 2
)//自定义运算符,创建用于计算阶乘的自定义运算符 @Test public void test10() { Operator factorial = new Operator("!", 1, true, Operator.PRECEDENCE_POWER + 1) { @Override public double apply(double... args) { final int arg = (int) args[0]; if ((double) arg != args[0]) { throw new IllegalArgumentException("Operand for factorial has to be an integer"); } if (arg < 0) { throw new IllegalArgumentException("The operand of the factorial can not be less than zero"); } double result = 1; for (int i = 1; i <= arg; i++) { result *= i; } return result; } }; double result = new ExpressionBuilder("3!") .operator(factorial) .build() .evaluate(); double expected = 6d; assertEquals(expected, result, 0d); }
创建一个自定义运算符逻辑运算符,如果第一个参数大于或等于第二个参数,则返回 1,否则返回 0。
/**
* 创建一个自定义运算符逻辑运算符,如果第一个参数大于或等于第二个参数,则返回 1,否则返回 0。
* @throws Exception
*/
@Test
public void testOperators3() throws Exception {
Operator gteq = new Operator(">=", 2, true, Operator.PRECEDENCE_ADDITION - 1) {
@Override
public double apply(double[] values) {
if (values[0] >= values[1]) {
return 1d;
} else {
return 0d;
}
}
};
Expression e = new ExpressionBuilder("1>=2").operator(gteq)
.build();
double evaluate = e.evaluate();
System.out.println(evaluate);
assertTrue(0d == e.evaluate());
e = new ExpressionBuilder("2>=1").operator(gteq)
.build();
assertTrue(1d == e.evaluate());
}
10、内置运算符
- 加法:
2 + 2
- 减法:
2 - 2
- 乘法:
2 * 2
- 除法:
2 / 2
- 指数:
2 ^ 2
- 一元减号,加号(符号运算符):
+2 - (-2)
- 模量:
2% 2
一元减号和幂运算符的优先级
一元减号运算符的优先级低于幂运算符的优先级。这意味着像-1^2
这样的表达式被评估为-(1^2)
而不是(-1)^2
。
运算和功能除以零
exp4j 在尝试除以零时抛出 ArithmeticException。在实现涉及除法的 Operator 或 Function 时,实现者必须确保抛出相应的 RuntimeException。
//Division by zero 除零的异常
@Test
public void test11() {
Operator reciprocal = new Operator("$", 1, true, Operator.PRECEDENCE_DIVISION) {
@Override
public double apply(final double... args) {
if (args[0] == 0d) {
throw new ArithmeticException("Division by zero!");
}
return 1d / args[0];
}
};
Expression e = new ExpressionBuilder("0$").operator(reciprocal).build();
double evaluate = e.evaluate();// <- this call will throw an ArithmeticException
System.out.println(evaluate);//2.5
}
11、内置函数
好多函数比较陌生,想了解的自己百度一下吧
abs
: absolute value 绝对值acos
: arc cosine 反余弦asin
: arc sine 反正弦atan
: arc tangent 反正切cbrt
: cubic root 立方根ceil
: nearest upper integer 最接近的上整数cos
: cosine 余弦cosh
: hyperbolic cosine 双曲余弦exp
: euler’s number raised to the power (e^x) 欧拉数的幂 (e^x)floor
: nearest lower integer 最接近的下整数log
: logarithmus naturalis (base e) 自然对数(以 e 为底)log10
: logarithm (base 10) 对数(以 10 为底)log2
: logarithm (base 2) 对数(以 2 为底)sin
: sinesinh
: hyperbolic sinesqrt
: square root 双曲正弦tan
: tangent 切线tanh
: hyperbolic tangent 双曲正切signum
: signum function 符号函数
12 、表达式验证
从 0.4.0 版本开始,exp4j 增加了一个验证表达式的功能。用户可以调用Expression.validate()
来执行相对快速的验证。validate 方法还接受一个布尔参数,指示是否应该检查空变量。
@Test
public void test12() {
Expression e = new ExpressionBuilder("x")
.variable("x")
.build();
ValidationResult res = e.validate();
assertFalse(res.isValid());
assertEquals(1, res.getErrors().size());
e.setVariable("x",1d);
res = e.validate();
assertTrue(res.isValid());
}
更多推荐
所有评论(0)