之前风控变量的计算引擎用的国内一个开源的IKExpression(为了让变量的计算可配置化),IKExpression的架构如下:
其中表达式编译和执行模块就是利用的逆波兰表达式计算,逆波兰表达式是在大学的编译原理中学习的。
举个例子:
如果计算机计算如下表达式:
1+(2*3-4)/2
如果不按照逆波兰式,计算机可以先对上面表达式进行解析生成一个符号树:
如果对这个求值的话就需要从叶子节点往根节点递归求值,如果一个表达式特别复杂那么这个树可能很深不利于计算机资源的合理利用。
可以用逆波兰表达式表示上面的计算,逆波兰表达式又叫做后缀表达式,波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为后缀表示。
上面计算转换成逆波兰表达式如下:
123*4-2/+
计算表达式转换为逆波兰式需要一个符号栈数据结构,和一个list即可,计算逆波兰表达式的时候也需要一个临时数据的存储栈结构。
1.首先需要将初始的表达式字符串转换成list,其中list的项可能为数字、计算符号,如果上面的表达式转换完成,结果如下:
1, +, (, 2, *, 3, -, 4, ), /, 2
2.将1步骤的list转换为逆波兰表达式:
首先需要定义一个栈(stack)和一个线性集合(list),具体步骤如下,其中竖着的为栈 横着的为集合:
(1) 从上面解析出的list从左往右依次取值,当取值为数字放到list中,这里出去来” 1 “放入list。
(2) 取出” + “符号进行判断,不是数字入栈。
(3) 取出” ( “非数字入栈。
(4) 取出数字” 2 ” 放入list。
(5) 取出” * ” 入栈
(6) 取出数字” 3 “放入list
(7) 取出” - ” 和栈顶的值(” * “)做比较,” * ” 优先级高于” - “将栈顶的弹出放到list然后再将” - “入栈。
(8) 取出数字” 4 ” 放到list
(9) 取出” ) ” 然后将栈中 “(“到”)”中的所有符号取出来添加到list中,这里就是取出” - “放到list中。
(10) 取出” / ” 和栈顶做比较,栈顶优先级低于它,入栈。
(11) 取出数字” 2 ” 放入list
(12) 遍历完成后将栈中剩下的弹出放到list中。
逆波兰表达式的计算如下:
这里就不画图了,就是从左遍历逆波兰式的list,取到值入栈,当遇到非数字型的就将栈中弹出两个值,用后弹出的值和先弹出的值做符号计算。
比如上面的表达式:
123*4-2/+
(1) 入栈1,然后入栈2,再入栈3,将栈中的3和2弹出来进行” * “运算得6再入栈
现在栈中就是 -> 1,6
(2) 入栈4,将栈中的4和6弹出,用后弹出的减去先弹出的,就是6减4的2,然后入栈2
现在栈中就是 -> 1,2
(3) 入栈2,弹出两个2进行除法得1,然后入栈1
现在栈中就是 -> 1,1
(4) 将栈中的值弹出进行相加得最终结果:2
java实现代码如下:
参考的:java实现逆波兰表达式算法
栈模拟类:
public class MyStack {
private List<String> l;
private int size;
public String top;
public MyStack() {
l = new ArrayList<String>();
size = 0;
top = null;
}
public int size() {
return size;
}
public void push(String s) {
l.add(s);
top = s;
size++;
}
public String pop() {
String s = l.get(size - 1);
l.remove(size - 1);
size--;
top = size == 0 ? null : l.get(size - 1);
return s;
}
}
主要程序:
public class Nbl {
private static MyStack ms1 = new MyStack();//生成逆波兰表达式的栈
private static MyStack ms2 = new MyStack();//运算栈
/**
* 将字符串转换为中序表达式
*/
public static List<String> zb(String s) {
List<String> ls = new ArrayList<String>();//存储中序表达式
int i = 0;
String str;
char c;
do {
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
ls.add("" + c);
i++;
} else {
str = "";
while (i < s.length() && (c = s.charAt(i)) >= 48
&& (c = s.charAt(i)) <= 57) {
str += c;
i++;
}
ls.add(str);
}
} while (i < s.length());
System.out.println(ls);
return ls;
}
/**
* 将中序表达式转换为逆波兰表达式
*/
public static List<String> parse(List<String> ls) {
List<String> lss = new ArrayList<String>();
for (String ss : ls) {
if (ss.matches("\\d+")) {
lss.add(ss);
} else if (ss.equals("(")) {
ms1.push(ss);
} else if (ss.equals(")")) {
while (!ms1.top.equals("(")) {
lss.add(ms1.pop());
}
ms1.pop();
} else {
while (ms1.size() != 0 && getValue(ms1.top) >= getValue(ss)) {
lss.add(ms1.pop());
}
ms1.push(ss);
}
}
while (ms1.size() != 0) {
lss.add(ms1.pop());
}
System.out.println(lss);
return lss;
}
/**
* 对逆波兰表达式进行求值
*/
public static int jisuan(List<String> ls) {
for (String s : ls) {
if (s.matches("\\d+")) {
ms2.push(s);
} else {
int b = Integer.parseInt(ms2.pop());
int a = Integer.parseInt(ms2.pop());
if (s.equals("+")) {
a = a + b;
} else if (s.equals("-")) {
a = a - b;
} else if (s.equals("*")) {
a = a * b;
} else if (s.equals("/")) {
a = a / b;
}
ms2.push("" + a);
}
}
return Integer.parseInt(ms2.pop());
}
/**
* 获取运算符优先级
* +,-为1 *,/为2 ()为0
*/
public static int getValue(String s) {
if (s.equals("+")) {
return 1;
} else if (s.equals("-")) {
return 1;
} else if (s.equals("*")) {
return 2;
} else if (s.equals("/")) {
return 2;
}
return 0;
}
public static void main(String[] args) {
System.out.println(jisuan(parse(zb("1+(2*3-4)/2"))));
}
}