逆波兰表达式:解锁关键词文本匹配的高效密码

182 阅读3分钟

逆波兰表达式:解锁关键词文本匹配的高效密码

在文本处理场景中,我们经常需要根据规则判断文本是否满足特定条件。本文通过一个实际案例,讲解如何利用逆波兰表达式解决复杂的关键词匹配问题。


一、业务场景分析

输入文本你们可以帮我调查一下
匹配规则帮我&(调查|处理)
规则说明:文本需同时满足以下两个条件

  1. 包含关键词帮我
  2. 包含调查处理中的任意一个

若文本满足规则,则返回命中,否则返回未命中


二、逆波兰表达式简介

逆波兰表达式(后缀表达式) 是一种数学表达式表示方法,其特点是将运算符放在操作数之后。例如,中缀表达式 A+B 对应的逆波兰表达式为 A B +。这种表示法无需括号即可明确运算顺序,适合用栈结构进行计算。


三、实现思路

  1. 规则解析
    将规则字符串分解为操作数和运算符。例如,帮我&(调查|处理) 解析为:["帮我", "&", "(", "调查", "|", "处理", ")"]

  2. 中缀转后缀表达式
    利用栈结构处理运算符优先级,将中缀表达式转换为逆波兰表达式。例如,帮我&(调查|处理) 转换为 帮我 调查 处理 | &

  3. 后缀表达式求值
    遍历逆波兰表达式,使用栈结构进行布尔运算,最终得到匹配结果。


四、代码实现

1. 规则分词

将规则字符串拆分为操作数和运算符列表。

public static List<String> tokenize(String rule) {
    List<String> tokens = new ArrayList<>();
    StringBuilder currentWord = new StringBuilder();
    for (char c : rule.toCharArray()) {
        if (c == '&' || c == '|' || c == '(' || c == ')') {
            if (currentWord.length() > 0) {
                tokens.add(currentWord.toString());
                currentWord.setLength(0);
            }
            tokens.add(String.valueOf(c));
        } else {
            currentWord.append(c);
        }
    }
    if (currentWord.length() > 0) {
        tokens.add(currentWord.toString());
    }
    return tokens;
}
2. 中缀表达式转后缀表达式

处理运算符优先级和括号。

public static List<String> infixToPostfix(List<String> infixTokens) {
    List<String> output = new ArrayList<>();
    Deque<String> stack = new LinkedList<>();
    Map<String, Integer> precedence = new HashMap<>();
    precedence.put("&", 2);
    precedence.put("|", 1);
    precedence.put("(", 0);

    for (String token : infixTokens) {
        if (isOperator(token) || token.equals("(") || token.equals(")")) {
            if (token.equals("(")) {
                stack.push(token);
            } else if (token.equals(")")) {
                while (!stack.isEmpty() && !stack.peek().equals("(")) {
                    output.add(stack.pop());
                }
                stack.pop(); // 弹出左括号
            } else { // 处理运算符
                while (!stack.isEmpty() && precedence.get(stack.peek()) >= precedence.get(token)) {
                    output.add(stack.pop());
                }
                stack.push(token);
            }
        } else { // 处理关键词
            output.add(token);
        }
    }

    while (!stack.isEmpty()) {
        output.add(stack.pop());
    }
    return output;
}

private static boolean isOperator(String token) {
    return token.equals("&") || token.equals("|");
}
3. 后缀表达式求值

根据文本内容计算布尔结果。

public static boolean evaluatePostfix(List<String> postfixTokens, String text) {
    Deque<Boolean> stack = new LinkedList<>();
    for (String token : postfixTokens) {
        if (isOperator(token)) {
            boolean b = stack.pop();
            boolean a = stack.pop();
            if (token.equals("&")) {
                stack.push(a && b);
            } else if (token.equals("|")) {
                stack.push(a || b);
            }
        } else {
            stack.push(text.contains(token));
        }
    }
    return stack.pop();
}
4. 主函数测试

结合业务场景验证结果。

public static void main(String[] args) {
    String text = "你们可以帮我调查一下";
    String rule = "帮我&(调查|处理)";
    
    List<String> infixTokens = tokenize(rule);
    List<String> postfixTokens = infixToPostfix(infixTokens);
    boolean result = evaluatePostfix(postfixTokens, text);
    
    System.out.println("文本是否命中规则:" + result); // 输出:true
}

五、总结

通过逆波兰表达式,我们能够优雅地处理复杂的逻辑匹配规则。其核心优势在于:

  1. 消除括号带来的优先级歧义。
  2. 通过栈结构实现高效计算。

本文代码可直接应用于关键词过滤、规则引擎等场景。