• 工作中的那些坑(2)——逆波兰表达式


    工作项目里用到线性回归算法,用于计算账户的分值,表明某账户是否是有风险的账户。其中参数都配好了,代码里直接用逆波兰表达式解析即可。

    逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

    编程实现的话,其实也很简单,两个栈即可实现,姑且把一个栈叫做 operators,保存运算符,另一个栈叫做output,保存最终的表达式。

    就三个要点:

    • 数字直接入output
    • 运算符要与operators栈顶比较,优先级大则入栈,小于或等于则operators出栈后再入栈
    • operators栈顶若是(则无条件入栈

    借用一张网上的图片来说明:

    图解逆波兰表达式

    其代码如下:

    package test;
    
    import java.util.Stack;
    
    public class ReversePolishNotation {
        public static void main(String[] args) {
            // 测试用例
            // String str = "1+2*3-4*5-6+7*8-9"; //123*+45*-6-78*+9-
            String str = "a*(b-c*d)+e-f/g*(h+i*j-k)"; // abcd*-*e+fg/hij*+k-*-
            // String str = "6*(5+(2+3)*8+3)"; //6523+8*+3+*
            // String str = "a+b*c+(d*e+f)*g"; //abc*+de*f+g*f
    
            Stack<Character> operators = new Stack<>(); // 运算符
            Stack<Object> output = new Stack<Object>(); // 输出结果
            rpn(operators, output, str);
            for (Object c : output) {
                System.out.print(c+" ");
            }
            System.out.println(" ");
            System.out.println(output);
        }
    
        public static void rpn(Stack<Character> operators, Stack<Object> output, String str) {
            char[] chars = str.toCharArray();
            int pre = 0;
            boolean digital; // 是否为数字(只要不是运算符,都是数字),用于截取字符串
            int len = chars.length;
            int bracket = 0; // 左括号的数量
    
            for (int i = 0; i < len;) {
                pre = i;
                digital = Boolean.FALSE;
                // 截取数字
                while (i < len && !Operator.isOperator(chars[i])) {
                    i++;
                    digital = Boolean.TRUE;
                }
    
                //数字,直接压入结果栈
                if (digital) {
                    output.push(str.substring(pre, i));
                } 
                //非数字,即操作符
                else {
                    char o = chars[i++]; // 运算符
                    if (o == '(') {
                        bracket++;
                    }
                    if (bracket > 0) {
                        if (o == ')') {
                            //操作符栈不为空,弹出栈顶直到遇到(,丢弃一对括号
                            while (!operators.empty()) {
                                char top = operators.pop();
                                if (top == '(') {
                                    break;
                                }
                                output.push(top);
                            }
                            bracket--;
                        } else {
                            // 如果栈顶为 ( ,则直接添加,不顾其优先级
                            // 如果之前有 ( ,但是 ( 不在栈顶,则需判断其优先级,如果优先级比栈顶的低,则依次出栈
                            while (!operators.empty() && operators.peek() != '('
                                    && Operator.cmp(o, operators.peek()) <= 0) {
                                output.push(operators.pop());
                            }
                            operators.push(o);
                        }
                    } else {
                        while (!operators.empty() && Operator.cmp(o, operators.peek()) <= 0) {
                            output.push(operators.pop());
                        }
                        operators.push(o);
                    }
                }
    
            }
            // 遍历结束,将运算符栈全部压入output
            while (!operators.empty()) {
                output.push(operators.pop());
            }
        }
    }
    
    enum Operator {
        ADD('+', 1), SUBTRACT('-', 1), MULTIPLY('*', 2), DIVIDE('/', 2), LEFT_BRACKET('(', 3), RIGHT_BRACKET(')', 3); // 括号优先级最高
        char value;
        int priority;
    
        Operator(char value, int priority) {
            this.value = value;
            this.priority = priority;
        }
    
        /**
         * 比较两个符号的优先级
         *
         * @param c1
         * @param c2
         * @return c1的优先级是否比c2的高,高则返回正数,等于返回0,小于返回负数
         */
        public static int cmp(char c1, char c2) {
            int p1 = 0;
            int p2 = 0;
            for (Operator o : Operator.values()) {
                if (o.value == c1) {
                    p1 = o.priority;
                }
                if (o.value == c2) {
                    p2 = o.priority;
                }
            }
            return p1 - p2;
        }
    
        /**
         * 枚举出来的才视为运算符,用于扩展
         *
         * @param c
         * @return
         */
        public static boolean isOperator(char c) {
            for (Operator o : Operator.values()) {
                if (o.value == c) {
                    return true;
                }
            }
            return false;
        }
    }

    本来事情到这里已经结束,突然来了新的需求:账户算出来的分数较为无序,于是考虑用sigmoid函数将其映射到(0,1)区间内,在乘以系数使其显示更为直观。为了使整个表达式更将通用,要求做到同时能解析sigmoid函数,即:原来单纯解析常量、变量、运算符的逆波兰表达式已经不能直接解析新增的sigmoid函数(即表达式),另外对嵌套的情况也没做处理,所以需要重新设计一个更为通用的语法解析器,根据具体的需求来决定设计成什么样,这里就不多做赘述。

    另外介绍下sigmoid函数,按照wiki上的解释:A sigmoid function is a mathematical function having a characteristic "S"-shaped curve or sigmoid curve. Often, sigmoid function refers to the special case of the logistic function shown in the first figure and defined by the formula

     

    其曲线如图

     

    简单来说,该函数将一个区间平滑的映射到(0,1)区间内,是不是联想到了概率呢?以后可能会更新一系列机器学习的文章对此函数做进一步说明。

  • 相关阅读:
    $Django 中间件 csrf
    $Django cookies与session--解决无连接无状态问题, session配置
    $Django Form组件
    $Django Paginator分页器 批量创建数据
    $Djangon admin界面 添加表 增删查改
    $Django ajax简介 ajax简单数据交互,上传文件(form-data格式数据),Json数据格式交互
    $Django 多对多-自定义第三张表 基于双下划线的跨表查询(补充)
    $Django 客户端->wsgi->中间组件->urls->views(model,template) 总结+补充(事物,choices,inclusion_tag)!
    $Django 聚合函数、分组查询、F,Q查询、orm字段以及参数
    经典的C++库【转帖】
  • 原文地址:https://www.cnblogs.com/huashu/p/7461185.html
Copyright © 2020-2023  润新知