一篇掌握Jep灵活应用,数学公式自定义

一篇掌握Jep灵活应用,数学公式自定义本文教会如何能在业务场景下使用 jep

大家好,欢迎来到IT知识分享网。

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

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

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

业务需求

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

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

jep重要源码及使用

最开始忘记给出jep依赖包了,这次更新补充一下

<!-- jep依赖--> <dependency> <groupId>org.scijava</groupId> <artifactId>jep</artifactId> <version>2.4.2</version> </dependency>

首先我们先简介一下jep。

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

这些都是封装好的函数。

 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:

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"); } }

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

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

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

简单代码示例

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

@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灵活应用,数学公式自定义

不足的地方

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

一篇掌握Jep灵活应用,数学公式自定义

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

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

修改源码

我规定传入的时候是:

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

JepPlus

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))); } } } 

快排工具类:

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存入他的成员变量里面

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(); } } 

自定义公式

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

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; } } 

应用示例:

 @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); }

一篇掌握Jep灵活应用,数学公式自定义

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/133582.html

(0)
上一篇 2025-07-21 19:26
下一篇 2025-07-21 19:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信