• 【java表达式引擎】三、轻量级表达式引擎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.objecthuntergroupId> 
        <artifactId>exp4jartifactId> 
        <version>0.4.8version> 
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、最全阿里镜像

    大家在依赖坐标的时候可能会下载不下来,推荐配置上阿里的所有镜像仓库,可以再阿里仓库官网查看

    https://developer.aliyun.com/mvn/guide

    
    
    
    <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/repositorylocalRepository>
      
    
      <mirrors>
       	<mirror>
    		<id>aliyun-centralid>
    		<mirrorOf>centralmirrorOf>
    		<name>aliyun centralname>
    		<url>https://maven.aliyun.com/repository/centralurl>
    	mirror>
        <mirror>
    		<id>aliyun-publicid>
    		<mirrorOf>publicmirrorOf>
    		<name>aliyun publicname>
    		<url>https://maven.aliyun.com/repository/publicurl>
    	mirror>
    	 
     <mirror>
    		<id>aliyun-publicid>
    		<mirrorOf>jcentermirrorOf>
    		<name>aliyun jcentername>
    		<url>https://maven.aliyun.com/repository/publicurl>
    	mirror>
    
    
    	<mirror>
    		<id>aliyun-springid>
    		<mirrorOf>aliyun-springmirrorOf>
    		<name>aliyun springname>
    		<url>https://maven.aliyun.com/repository/springurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-spring-pluginid>
    		<mirrorOf>aliyun-spring-pluginmirrorOf>
    		<name>aliyun spring-pluginname>
    		<url>https://maven.aliyun.com/repository/spring-pluginurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-apache-snapshotsid>
    		<mirrorOf>aliyun-apache-snapshotsmirrorOf>
    		<name>aliyun apache-snapshotsname>
    		<url>https://maven.aliyun.com/repository/apache-snapshotsurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-googleid>
    		<mirrorOf>aliyun-googlemirrorOf>
    		<name>aliyun googlename>
    		<url>https://maven.aliyun.com/repository/googleurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-gradle-pluginid>
    		<mirrorOf>aliyun-gradle-plugimirrorOf>
    		<name>aliyun gradle-pluginname>
    		<url>https://maven.aliyun.com/repository/gradle-pluginurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-jcenterid>
    		<mirrorOf>aliyun-jcentermirrorOf>
    		<name>aliyun jcentername>
    		<url>https://maven.aliyun.com/repository/jcenterurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-releasesid>
    		<mirrorOf>aliyun-releasesmirrorOf>
    		<name>aliyun releasesname>
    		<url>https://maven.aliyun.com/repository/releasesurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-snapshotsid>
    		<mirrorOf>aliyun-snapshotsmirrorOf>
    		<name>aliyun snapshotsname>
    		<url>https://maven.aliyun.com/repository/snapshotsurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-grails-coreid>
    		<mirrorOf>aliyun-grails-coremirrorOf>
    		<name>aliyun grails-corename>
    		<url>https://maven.aliyun.com/repository/grails-coreurl>
    	mirror>
    
    	<mirror>
    		<id>aliyun-mapr-publicid>
    		<mirrorOf>aliyun-mapr-publicmirrorOf>
    		<name>aliyun mapr-publicname>
    		<url>https://maven.aliyun.com/repository/mapr-publicurl>
    	mirror>
      mirrors>
      <profiles>
      profiles>
    settings>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    三、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
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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);
    
    • 1
    • 2
    • 3
    • 4

    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
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
       }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

    创建一个自定义运算符逻辑运算符,如果第一个参数大于或等于第二个参数,则返回 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());
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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: sine
    • sinh: hyperbolic sine
    • sqrt: 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());
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    如何使用 Angular 服务器端渲染的 Transfer State Service
    [C/C++]天天酷跑超详细教程-中篇
    python向量之间相似性的计算方法
    今日论文阅读2022-11-10
    基于matlab求两个数最大公约数函数gcd
    SAP-PP:基础概念笔记-5(物料主数据的MRP1~4视图)
    Google桌面与BBdoc文件管理助手对比分析
    无涯教程-JavaScript - ATAN函数
    Managing Supply and Demand Balance Through Machine Learning-笔记
    我的创作纪念日
  • 原文地址:https://blog.csdn.net/weixin_43333483/article/details/126806681