灵活自定义数学公式,业务场景高级使用jep

自定义数学公式:jep灵活运用。

由于最近事情比较繁忙,所以就没有静下心或者有时间去将所学的东西整理分享。今天忙里偷闲,分享一篇关于jep的灵活使用。

浏览了很多关于jep的文章,感觉都是在原本jep的基础上去应用,计算一些数据。比较局限,那么我在根据自己的业务需求的时候,看了下jep的源码,并且添加了部分代码去更加灵活的应用。

业务需求

为什么想到去用jep呢,是因为我在项目过程中遇到了一个,希望可以自己自定义统计量公式的需求,例如平均值,方差,还有根据他们的值进行一些权重分配,混合运算的要求,那么需要根据存储在数据库中的字符串表达式,来在后端进行动态的识别,并且读取数据,根据公式字符串,返回对应统计量的结果。

我们希望传入动态的数据,根据不同的公式,计算出值。

jep重要源码及使用

首先我们先简介一下jep。

JEP是Java expression parser的简称,即java表达式分析器,Jep是一个用来转换和计算数学表达式的java库。通过这个程序包,用户可以以字符串的形式输入一个、任意的公式,然后快速地计算出结果。Jep支持用户自定义变量、常量和函数。包括许多常用的数学函数和常量。

这些都是封装好的函数。

java 复制代码
	public void addStandardFunctions() {
		//add functions to Function Table
		funTab.put("sin", new Sine());
		funTab.put("cos", new Cosine());
		funTab.put("tan", new Tangent());
		funTab.put("asin", new ArcSine());
		funTab.put("acos", new ArcCosine());
		funTab.put("atan", new ArcTangent());
		funTab.put("atan2", new ArcTangent2());

		funTab.put("sinh", new SineH());
		funTab.put("cosh", new CosineH());
		funTab.put("tanh", new TanH());
		funTab.put("asinh", new ArcSineH());
		funTab.put("acosh", new ArcCosineH());
		funTab.put("atanh", new ArcTanH());

		funTab.put("log", new Logarithm());
		funTab.put("ln", new NaturalLogarithm());
		funTab.put("exp", new Exp());
		funTab.put("pow", new Power());

		funTab.put("sqrt",new SquareRoot());
		funTab.put("abs", new Abs());
		funTab.put("mod", new Modulus());
		funTab.put("sum", new Sum());

		funTab.put("rand", new org.nfunk.jep.function.Random());
		
		// rjm additions
		funTab.put("if", new If());
		funTab.put("str", new Str());
		
		// rjm 13/2/05
		funTab.put("binom", new Binomial());
		
		// rjm 26/1/07
		funTab.put("round",new Round());
		funTab.put("floor",new Floor());
		funTab.put("ceil",new Ceil());
	}

简单的来说,就是可以用算术运算符代替之前的java的公式,显示和逻辑编写更加清晰简单。

我们继续深层的读取源码,会发现它继承了一个类:

PostfixMathCommand:

java 复制代码
public class PostfixMathCommand implements PostfixMathCommandI {
    protected int numberOfParameters = 0;
    protected int curNumberOfParameters = 0;

    public PostfixMathCommand() {
    }

    protected void checkStack(Stack inStack) throws ParseException {
        if (null == inStack) {
            throw new ParseException("Stack argument null");
        }
    }

    public int getNumberOfParameters() {
        return this.numberOfParameters;
    }

    public void setCurNumberOfParameters(int n) {
        this.curNumberOfParameters = n;
    }

    public boolean checkNumberOfParameters(int n) {
        if (this.numberOfParameters == -1) {
            return true;
        } else {
            return this.numberOfParameters == n;
        }
    }

    public void run(Stack s) throws ParseException {
        throw new ParseException("run() method of PostfixMathCommand called");
    }
}

那么这里面比较有用的两个参数:

protected int numberOfParameters = 0;

protected int curNumberOfParameters = 0;

一个表示可变参数,一个表示参数个数。

为什么重要呢,因为这里面的函数计算,会是栈的计算,也就是会把传入参数弹出之后,将计算结果存入栈里返回,如果像定义一个公式sum(data)/count(data),那么count运算时候,弹栈的时候,就不能把sum存入的结果也弹出了,为什么会这么说,因为我最开始运用弹出数据控制的时候,就是用的inStack.isEmpty()方法来控制循环的,所以最后会返回NaN的结果,我们需要根据curNumberOfParameters来进行循环。

简单代码示例

看了上面的说明,如果是初次接触可能会很懵,没关系,我们看一下简单实例来理解。

csharp 复制代码
@Test
    public void getValueNet(){
        JEP jep = new JEP();
        // 添加常用函数
        jep.addStandardFunctions();
        // 添加常用常量
        jep.addStandardConstants();

        String exp = "M12*3.14/4"; //给变量赋值

         jep.addVariable("M12", 6.0);

        try { //执行
            jep.parseExpression(exp);
            double result = jep.getValue();
            System.out.println("计算结果: " + result);
        } catch (Throwable e) {
            System.out.println("An error occured: " + e.getMessage());
        }
    }

这便是最简单的jep代码示例:

下面我通过图片来解释:

​编辑

不足的地方

如果说,只是计算一两个数,给定的公式,给定的数据集,而且必须很小,才能这样计算,因为我们看源码发现,这个jep原本add变量的时候,竟然是一个一个加的。

这里的symTab就是它存储变量,也就是后面用来公式识别的一个成员变量。

说白了,就是太笨拙了,而我们后端需要的数据,是变化的,因此,我们需要更改源码,并且编写工具类,来提升它。

修改源码

我规定传入的时候是:

count(A1,A2,A3,....)等等这样的数据

JepPlus

kotlin 复制代码
public class JepPlus extends JEP {

    public JepPlus(){
        super();
    }
    public Double addVariable(String name, double value) {
        Double object = new Double(value);
        //这个就是读取对象放进去,给对应的字符串对应值
        this.symTab.makeVarIfNeeded(name, object);
        return object;
    }

    public void addArrayVariable(List<String> value){
       //现在接收到一个数组,data。data里有三个数据
        // 现在我们遍历数组,给数组的每个对象按顺序起一名字并把对应的值放入进去
       for(int i=0;i<value.size();i++){
           this.symTab.makeVarIfNeeded("A"+(i+1),Double.parseDouble(value.get(i)));
       }
    }
}

快排工具类:

java 复制代码
package com.neu.statistics.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class QuickSortUtil {

    public static void main(String[] args) {
        List<Double> arr= new ArrayList<>();

        System.out.println("原始数组:"+ Arrays.toString(arr.toArray()));
        //获取数组arr的长度

        List<Double> newList = quickSort(arr, 0, arr.size() - 1);
        System.out.println("排序后的数组:"+ Arrays.toString(newList.toArray()));

    }
    public static List<Double> quickSort(List<Double> arr, int left, int right) {

        //递归结束条件left < right
        if (left < right) {
            // 通过分区函数得到基准元素的索引
            int pivotIndex = partition(arr, left, right);
            //递归对基准元素左边的子数组进行快速排序
            quickSort(arr, left, pivotIndex - 1);
            //递归对基准元素右边的子数组进行快速排序
            quickSort(arr, pivotIndex + 1, right);
        }

        return arr;

    }

    public static int partition(List<Double> arr, int left, int right) {
        // 选择最后一个元素作为基准元素
        double pivot = arr.get(right);
        int i = left;
        //int[] arr = new int[]{5,7,3,3,6,4};

        //循环数组,如果满足条件,则将满足条件的元素交换到arr[i],同时i++,循环完成之后i之前的元素则全部为小于基准元素的元素
        for (int j = left; j < right; j++) {
            if (arr.get(j) < pivot) {
                if (j != i) {
                    swap(arr, i, j);
                }
                i++;//数交换之后,需要左指针i右移
            }
        }

        // 交换 arr[i] 和基准元素
        swap(arr, i, right);
        //返回基准元素的下标
        return i;
    }

    public static void swap(List<Double> arr, int left, int right) {
        double temp = arr.get(left);
        //交换list中的两个元素
        arr.set(left, arr.get(right));
        arr.set(right, temp);
    }


}

字符串转换工具类

我这里定义我的数据库存储数据,数据集合统一用data来表示。以后也可以根据业务不同需求来进行扩展。

由于我们数据库存储的是sum(data)/count(data)

那么data是我们传入的一组字符串数据,我们需要在识别到data之后,把他变成sum(A1,A2,A3)/count(A1,A2,A3),这样jep就可以将变量A1,A2,A3存入他的成员变量里面

java 复制代码
package com.neu.statistics.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DynamicStringConversion {

    public static void main(String[] args) {
        // 原始表达式
        String formula = "sum(data)/count(data)";

        // 模拟从数据库或其他源获取数据
        List<String> dataList = new ArrayList<>();
        dataList.add("1");
        dataList.add("2");
        dataList.add("3");

        // 动态转换表达式
        String dynamicFormula = convertFormula(formula, dataList);

        // 输出转换后的表达式
        System.out.println("初始字符串: " + formula);
        System.out.println("转换后的字符串: " + dynamicFormula);
    }

    // 动态替换表达式中的 data
    public static String convertFormula(String formula, List<String> dataList) {
        // 匹配 data 字符串的正则表达式
        String regex = "\bdata\b";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(formula);

        // 动态替换每个匹配的 data
        StringBuffer result = new StringBuffer();
        while (matcher.find()) {
            // 将匹配到的 data 替换为 a1, a2, a3 格式
            matcher.appendReplacement(result, buildReplacement(dataList));
        }
        matcher.appendTail(result);

        return result.toString();
    }

    // 根据 dataList 构建替换字符串,使用 a1, a2, ... 的变量名
    public static String buildReplacement(List<String> dataList) {
        // 构建 a1, a2, a3 格式
        StringBuilder replacement = new StringBuilder();
        for (int i = 0; i < dataList.size(); i++) {
            replacement.append("A").append(i + 1);
            if (i < dataList.size() - 1) {
                replacement.append(", ");
            }
        }
        return replacement.toString();
    }
}

自定义公式

目前我写了平均值,最大值,最小值方差等公式,以后我会根据业务需求继续拓展,目前给出这些。

scss 复制代码
package com.neu.statistics.utils;

import org.nfunk.jep.ParseException;
import org.nfunk.jep.function.PostfixMathCommand;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

public class FormulaCalculator {


    public String calculateFormula(List<String> valueList, String formula) throws ParseException {
        String exp = DynamicStringConversion.convertFormula(formula, valueList);
        JepPlus jep = new JepPlus();
        jep.addStandardFunctions(); // 添加标准函数(可选)
        jep.addStandardConstants(); // 添加标准常量(可选)
        // 注册自定义的 sum 和 count 函数
        jep.addFunction("sum", new SumFunction());
        jep.addFunction("count", new CountFunction());
        jep.addFunction("min",new MinFunction());
        jep.addFunction("max",new MaxFunction());
        jep.addFunction("median",new MedianFunction());
        jep.addFunction("variance",new VarianceFunction());
        jep.addArrayVariable(valueList);
        jep.parseExpression(exp);
        // 输出结果
        return String.valueOf(jep.getValue());
    }

}




//求和
class SumFunction extends PostfixMathCommand {


    public SumFunction(){
        numberOfParameters = -1; //可变参数
    }

    @Override
    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for(int i=0;i<this.curNumberOfParameters;i++){
            params.add(inStack.pop());
        }
        inStack.push(this.sum(params));
    }

    public Object sum(List<Object> params) throws ParseException {
        double sum=0.0;
        for (Object param : params) {
            if (param instanceof Number) {
                sum += ((Number)param).doubleValue();
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        return sum;
    }

}

//求总数
class CountFunction extends PostfixMathCommand{

    public CountFunction(){
        numberOfParameters = -1; //可变参数
    }
    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for(int i=0;i<this.curNumberOfParameters;i++){
            params.add(inStack.pop());
        }
//        while(!inStack.isEmpty()){
//            params.add(inStack.pop());
//        }
        inStack.push(this.count(params));
    }
    public Object count(List<Object> params) throws ParseException {
        double count=0.0;
        for (Object param : params) {
            if (param instanceof Number) {
                count += 1;
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        return count;
    }

}

//求最小值
class MinFunction extends PostfixMathCommand {

    public MinFunction() {
        numberOfParameters = -1; //可变参数
    }

    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for (int i = 0; i < this.curNumberOfParameters; i++) {
            params.add(inStack.pop());
        }
        inStack.push(this.min(params));

    }
    public Object min(List<Object> params) throws ParseException{
        //快速排序,返回最小值,也就是数组的第一个元素
        //将object转换为double
        List<Double> list = new ArrayList<>();
        for (Object param : params) {
            if (param instanceof Number) {
                list.add(((Number) param).doubleValue());
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        List<Double> newList=QuickSortUtil.quickSort(list,0,list.size()-1);
        Object result=newList.get(0);
        return result;
    }

}

//求最大值
class MaxFunction extends PostfixMathCommand {

    public MaxFunction() {
        numberOfParameters = -1; //可变参数
    }

    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for (int i = 0; i < this.curNumberOfParameters; i++) {
            params.add(inStack.pop());
        }

        inStack.push(this.max(params));
    }
    public Object max(List<Object> params) throws ParseException{
        //快速排序,返回最小值,也就是数组的第一个元素
        //将object转换为double
        List<Double> list = new ArrayList<>();
        for (Object param : params) {
            if (param instanceof Number) {
                list.add(((Number) param).doubleValue());
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        List<Double> newList=QuickSortUtil.quickSort(list,0,list.size()-1);
        Object result=newList.get(list.size()-1);
        return result;
    }

}

//中位数
class MedianFunction extends PostfixMathCommand {

    public MedianFunction() {
        numberOfParameters = -1; //可变参数
    }

    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for (int i = 0; i < this.curNumberOfParameters; i++) {
            params.add(inStack.pop());
        }
        inStack.push(this.median(params));
    }
    public Object median(List<Object> params) throws ParseException{
        //快速排序,返回最小值,也就是数组的第一个元素
        //将object转换为double
        List<Double> list = new ArrayList<>();
        for (Object param : params) {
            if (param instanceof Number) {
                list.add(((Number) param).doubleValue());
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        List<Double> newList=QuickSortUtil.quickSort(list,0,list.size()-1);
        int length=newList.size();
        if(newList.size()%2==0){
            Object result=(newList.get(length/2)+newList.get(length/2-1))/2;
            return result;
        }
        else{
            Object result=newList.get(length/2);
            return result;
        }
    }

}

//方差
class VarianceFunction extends PostfixMathCommand {

    public VarianceFunction() {
        numberOfParameters = -1; //可变参数
    }

    public void run(Stack inStack) throws ParseException {
        //检查栈
        this.checkStack(inStack);
        List<Object> params = new ArrayList<>();
        for (int i = 0; i < this.curNumberOfParameters; i++) {
            params.add(inStack.pop());
        }
        inStack.push(this.variance(params));
    }
    public Object variance(List<Object> params) throws ParseException{
        SumFunction sf=new SumFunction();
        double sum=(double)sf.sum(params);
        CountFunction cf=new CountFunction();
        double count=(double)cf.count(params);
        double average=sum/count;
        //快速排序,返回最小值,也就是数组的第一个元素
        //将object转换为double
        List<Double> list = new ArrayList<>();
        for (Object param : params) {
            if (param instanceof Number) {
                list.add(((Number) param).doubleValue());
            } else {
                throw new ParseException("Invalid parameter type");
            }
        }
        List<Double> newList=QuickSortUtil.quickSort(list,0,list.size()-1);
        double sumOfSquaredDifferences = 0.0;
        for(double value:newList){
            double difference=value-average;
            sumOfSquaredDifferences+=difference*difference;
        }
        Object result=sumOfSquaredDifferences/count;
        return result;
    }

}

应用示例:

ini 复制代码
    @Test
    public void test1() throws ParseException {
        List<String> list=new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        String formula = "variance(data)";
        FormulaCalculator formulaCalculator=new FormulaCalculator();
        String result = formulaCalculator.calculateFormula(list,formula);
        System.out.println(result);
    }

相关推荐
王二端茶倒水15 分钟前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
夜色呦1 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
爱敲代码的小冰1 小时前
spring boot 请求
java·spring boot·后端
java小吕布3 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
Goboy3 小时前
工欲善其事,必先利其器;小白入门Hadoop必备过程
后端·程序员
李少兄3 小时前
解决 Spring Boot 中 `Ambiguous mapping. Cannot map ‘xxxController‘ method` 错误
java·spring boot·后端
代码小鑫4 小时前
A031-基于SpringBoot的健身房管理系统设计与实现
java·开发语言·数据库·spring boot·后端
Json____4 小时前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库
monkey_meng4 小时前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss4 小时前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby