递归下降算法长用于数据表达式匹配。之前只听说和使用过递归算法,递归算法就是在函数内部调用函数本身。
在实际工作中有下边这样的需求,想不到用什么算法来实现,故将需求输入给deepseek,deepseek给出的答案中就用到了递归下降算法:
在软件开发中有这样一个需求,开发语言是c++,用户可以通过配置指定任务依赖的事件,比如配置e1|e2|e3的意思是,e1、e2、e3 3个事件,只要有一个到来,那么条件就是满足的,任务可以执行;再比如,用户配置e1&e2&e3,表示,只有e1、e2、e3 3个事件都道来,任务才可以执行;在比如,用户还可以配置(2*e1 | e2) & e3,这样复杂的逻辑,表示e3是必要的,另外e1到来两次或者e2到来一次也是必要的。事件的个数不仅仅是3个,还可以更多,逻辑关系包含|表示或,&表示与,*表示时间到来的次数,逻辑关系可以任意组合。对于这样的需求,代码应该怎么实现。
递归算法:
函数内部调用函数本身,即函数a中调用函数a。
递归下降算法:
函数a调用函数b,函数b调用函数c,...函数n调用函数n+1,函数n+1调用函数a。
代码记录如下,其中类Parser中使用递归下降算法。
#include <memory>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <stdexcept>
#include <iostream>
// ========================== 抽象语法树(AST)节点定义 ==========================
// 所有节点的基类,定义统一的评估接口
class Node {
public:
virtual ~Node() = default;
// 评估方法:根据当前事件状态判断条件是否满足
virtual bool evaluate(const std::map<std::string, int>& events) const = 0;
};
// 事件节点:表示需要特定次数的事件
class EventNode : public Node {
std::string name; // 事件名称(如"e1")
int required; // 需要达到的次数
public:
EventNode(std::string n, int r) : name(std::move(n)), required(r) {}
bool evaluate(const std::map<std::string, int>& events) const override {
auto it = events.find(name);
// 检查事件是否已到达且次数满足要求
return it != events.end() && it->second >= required;
}
};
// 逻辑与节点:左右子节点都需要满足
class AndNode : public Node {
std::unique_ptr<Node> left; // 左子树
std::unique_ptr<Node> right; // 右子树
public:
AndNode(std::unique_ptr<Node> l, std::unique_ptr<Node> r)
: left(std::move(l)), right(std::move(r)) {}
bool evaluate(const std::map<std::string, int>& events) const override {
// 递归评估左右子树是否都满足
return left->evaluate(events) && right->evaluate(events);
}
};
// 逻辑或节点:左右子节点任一满足
class OrNode : public Node {
std::unique_ptr<Node> left; // 左子树
std::unique_ptr<Node> right; // 右子树
public:
OrNode(std::unique_ptr<Node> l, std::unique_ptr<Node> r)
: left(std::move(l)), right(std::move(r)) {}
bool evaluate(const std::map<std::string, int>& events) const override {
// 递归评估左右子树是否至少有一个满足
return left->evaluate(events) || right->evaluate(events);
}
};
// ========================== 词法分析器(Tokenizer) ==========================
// Token类型枚举
enum TokenType {
NUMBER, // 数字(用于次数)
EVENT, // 事件名称(如e1)
AND, // 与操作符 &
OR, // 或操作符 |
MULTIPLY, // 乘号 *(用于次数)
LPAREN, // 左括号 (
RPAREN, // 右括号 )
END, // 输入结束
ERROR // 错误token
};
// Token结构体:包含类型、原始值和数字值(如果是数字)
struct Token {
TokenType type;
std::string value; // 原始字符串值
int num = 0; // 如果是数字token,存储数值
};
// 词法分析器:将输入字符串转换为token序列
class Tokenizer {
std::string input; // 原始输入字符串
size_t pos = 0; // 当前解析位置
public:
Tokenizer(std::string str) : input(std::move(str)) {}
// 获取下一个token
Token next() {
// 跳过空白字符
while (pos < input.size() && isspace(input[pos])) ++pos;
if (pos >= input.size()) return {END, ""};
char c = input[pos++];
// 处理单字符token
switch(c) {
case '(': return {LPAREN, "("};
case ')': return {RPAREN, ")"};
case '&': return {AND, "&"};
case '|': return {OR, "|"};
case '*': return {MULTIPLY, "*"};
}
// 处理数字(次数)
if (isdigit(c)) {
size_t start = pos-1;
while (pos < input.size() && isdigit(input[pos])) ++pos;
int num = std::stoi(input.substr(start, pos-start));
return {NUMBER, "", num};
}
// 处理事件名称(字母开头,包含字母数字和下划线)
if (isalpha(c)) {
size_t start = pos-1;
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_')) ++pos;
return {EVENT, input.substr(start, pos-start)};
}
// 未知字符
return {ERROR, ""};
}
};
// ========================== 语法分析器(Parser) ==========================
// 递归下降解析器:将token流转换为AST
class Parser {
Tokenizer& tokenizer; // 词法分析器引用
Token current; // 当前处理的token
// 前进到下一个token
void advance() { current = tokenizer.next(); }
// 表达式解析(处理OR操作)
std::unique_ptr<Node> parseExpr() {
auto left = parseAnd();
// 循环处理连续的OR操作(左结合)
while (current.type == OR) {
advance(); // 跳过OR
auto right = parseAnd();
// 构建OR节点,左子树为之前的表达式,右子树为新的AND表达式
left = std::make_unique<OrNode>(std::move(left), std::move(right));
}
return left;
}
// AND表达式解析(处理AND操作)
std::unique_ptr<Node> parseAnd() {
auto left = parseTerm();
// 循环处理连续的AND操作(左结合)
while (current.type == AND) {
advance(); // 跳过AND
auto right = parseTerm();
// 构建AND节点
left = std::make_unique<AndNode>(std::move(left), std::move(right));
}
return left;
}
// 项解析(处理次数*事件格式)
std::unique_ptr<Node> parseTerm() {
// 检查是否是数字开头(次数定义)
if (current.type == NUMBER) {
int count = current.num;
advance(); // 跳过数字
if (current.type != MULTIPLY)
throw std::runtime_error("Expected * after number");
advance(); // 跳过*
if (current.type != EVENT)
throw std::runtime_error("Expected event after *");
// 创建带次数的事件节点
auto node = std::make_unique<EventNode>(current.value, count);
advance(); // 跳过事件名
return node;
}
return parseFactor();
}
// 因子解析(处理括号和基本事件)
std::unique_ptr<Node> parseFactor() {
if (current.type == LPAREN) {
advance(); // 跳过(
auto node = parseExpr(); // 递归解析括号内表达式
if (current.type != RPAREN)
throw std::runtime_error("Mismatched parentheses");
advance(); // 跳过)
return node;
}
if (current.type == EVENT) {
// 创建默认次数为1的事件节点
auto node = std::make_unique<EventNode>(current.value, 1);
advance(); // 跳过事件名
return node;
}
throw std::runtime_error("Unexpected token");
}
public:
Parser(Tokenizer& t) : tokenizer(t) { advance(); } // 初始化时获取第一个token
std::unique_ptr<Node> parse() {
return parseExpr();
}
};
// ========================== 使用示例 ==========================
int main() {
// 示例1: (2*e1 | e2) & e3
{
std::cout << "===== 测试用例1 =====" << std::endl;
std::string config = "(2*e1 | e2) & e3";
Tokenizer tokenizer(config);
Parser parser(tokenizer);
auto ast = parser.parse();
// 测试用例1:e1触发2次,e3触发1次 -> 应满足
std::map<std::string, int> case1 = {{"e1", 2}, {"e3", 1}};
std::cout << "Case1: " << ast->evaluate(case1) << " (预期: true)\n";
// 测试用例2:e2触发1次,e3触发1次 -> 应满足
std::map<std::string, int> case2 = {{"e2", 1}, {"e3", 1}};
std::cout << "Case2: " << ast->evaluate(case2) << " (预期: true)\n";
// 测试用例3:e1触发1次,e3触发1次 -> 不满足
std::map<std::string, int> case3 = {{"e1", 1}, {"e3", 1}};
std::cout << "Case3: " << ast->evaluate(case3) << " (预期: false)\n";
}
// 示例2: a | b&(c | 3*d)
{
std::cout << "\n===== 测试用例2 =====" << std::endl;
std::string config = "a | b&(c | 3*d)";
Tokenizer tokenizer(config);
Parser parser(tokenizer);
auto ast = parser.parse();
// 测试用例1:a触发1次 -> 应满足
std::map<std::string, int> case1 = {{"a", 1}};
std::cout << "Case1: " << ast->evaluate(case1) << " (预期: true)\n";
// 测试用例2:b触发1次,d触发3次 -> 应满足
std::map<std::string, int> case2 = {{"b",1}, {"d",3}};
std::cout << "Case2: " << ast->evaluate(case2) << " (预期: true)\n";
// 测试用例3:b触发1次,c触发1次 -> 应满足
std::map<std::string, int> case3 = {{"b",1}, {"c",1}};
std::cout << "Case3: " << ast->evaluate(case3) << " (预期: true)\n";
// 测试用例4:b触发1次,d触发2次 -> 不满足
std::map<std::string, int> case4 = {{"b",1}, {"d",2}};
std::cout << "Case4: " << ast->evaluate(case4) << " (预期: false)\n";
}
return 0;
}