如何将含逻辑运算的字符串转为LINQ表达式验证集合有效性?
实现思路:把规则字符串转成LINQ可执行的校验逻辑
这个需求本质上是要实现一个简单的布尔表达式解析器,把字符串形式的规则转换成能直接和集合A做校验的逻辑。我来一步步拆解实现步骤:
第一步:预处理输入集合A
首先把字符串A转换成HashSet<string>,因为后续要频繁做「元素是否存在」的检查,HashSet的Contains方法效率远高于普通List。代码示例:
string A = "ab1, eb4, gr3"; var allowedItems = new HashSet<string>( A.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries) );
第二步:解析布尔规则字符串(B和C)
核心是把类似"(ut2 | eg1) & ab1"的中缀表达式,转换成可执行的布尔逻辑。这里分三个小步骤:
2.1 分词(Tokenize)
先把规则字符串拆成操作数(如ab1)、运算符(&/|)、括号这些独立的「令牌」,方便后续处理:
private static List<string> Tokenize(string expression) { var tokens = new List<string>(); var currentToken = new StringBuilder(); foreach (var c in expression) { if (char.IsWhiteSpace(c)) continue; // 遇到括号或运算符,先把当前累积的操作数加入列表 if (c == '(' || c == ')' || c == '&' || c == '|') { if (currentToken.Length > 0) { tokens.Add(currentToken.ToString()); currentToken.Clear(); } tokens.Add(c.ToString()); } else { currentToken.Append(c); } } // 把最后一个操作数加入列表 if (currentToken.Length > 0) tokens.Add(currentToken.ToString()); return tokens; }
2.2 转成后缀表达式(逆波兰式)
中缀表达式带括号,直接计算逻辑很麻烦,用Shunting-yard算法把它转成后缀表达式(逆波兰式),就能用栈轻松计算:
private static List<string> ToPostfix(List<string> tokens) { var output = new List<string>(); var stack = new Stack<string>(); // 定义运算符优先级:& 高于 | var precedence = new Dictionary<string, int> { { "&", 2 }, { "|", 1 } }; foreach (var token in tokens) { if (token == "(") { stack.Push(token); } else if (token == ")") { // 弹出栈内元素直到遇到左括号 while (stack.Peek() != "(") { output.Add(stack.Pop()); } stack.Pop(); // 弹出左括号,不加入输出 } else if (precedence.ContainsKey(token)) { // 弹出栈内优先级更高或相等的运算符 while (stack.Count > 0 && stack.Peek() != "(" && precedence[stack.Peek()] >= precedence[token]) { output.Add(stack.Pop()); } stack.Push(token); } else { // 操作数直接加入输出列表 output.Add(token); } } // 把栈内剩余的运算符加入输出 while (stack.Count > 0) output.Add(stack.Pop()); return output; }
2.3 计算后缀表达式结果
用栈遍历后缀表达式,每个操作数对应检查集合A是否包含它,然后根据运算符执行布尔运算:
private static bool EvaluatePostfix(List<string> postfix, HashSet<string> allowedItems) { var stack = new Stack<bool>(); foreach (var token in postfix) { if (token == "&") { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a && b); } else if (token == "|") { var b = stack.Pop(); var a = stack.Pop(); stack.Push(a || b); } else { // 检查当前项是否在A的集合中 stack.Push(allowedItems.Contains(token)); } } return stack.Pop(); }
第三步:整合校验逻辑
把必选规则B和禁用规则C的结果结合起来:必选规则必须满足,禁用规则必须不满足。完整调用示例:
// 预处理A string A = "ab1, eb4, gr3"; var allowedItems = new HashSet<string>( A.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries) ); // 校验必选规则B string B = "(ut2 | eg1) & ab1"; var bTokens = Tokenize(B); var bPostfix = ToPostfix(bTokens); bool meetsRequired = EvaluatePostfix(bPostfix, allowedItems); // 校验禁用规则C string C = "ke2 & pa5"; var cTokens = Tokenize(C); var cPostfix = ToPostfix(cTokens); bool violatesForbidden = EvaluatePostfix(cPostfix, allowedItems); // 最终校验结果:符合必选规则 且 不违反禁用规则 bool isValid = meetsRequired && !violatesForbidden;
进阶优化点
- 如果需要和EF等ORM结合,可以把
EvaluatePostfix改成构建Expression<Func<HashSet<string>, bool>>,这样表达式可以被翻译成SQL执行。 - 加入错误处理:比如校验不匹配的括号、非法运算符、不存在的操作数等,避免程序崩溃。
- 扩展支持
!(非)运算符,满足更复杂的规则需求。
内容的提问来源于stack exchange,提问作者fox
相关产品推荐
相关产品推荐

