动态 if 语句 - 复杂筛选
本文关键字:复杂 筛选 语句 if 动态 | 更新日期: 2023-09-27 18:32:50
我有一个 C# 项目,允许用户使用正则表达式对数据创建过滤器。 他们可以根据需要添加任意数量的过滤器。 每个筛选器都包含一个字段和一个用户键入的正则表达式。
现在它适用于所有 AND 逻辑。 我遍历每个过滤器,如果不匹配,我设置 skip = true 并脱离循环。 然后,如果 skip == true,我会跳过该记录并且不包含它。 因此,每个筛选器都必须匹配才能包含该字段。
但是,现在他们希望能够添加更复杂的逻辑规则。 例如,如果他们创建了 4 个过滤规则。 他们希望能够指定:1 和 2 和(3 或 4(或者他们可能想指定1 或 2 或 3 或 4或者他们可能想指定(1 和 2 和 3(或 4等等...我想你明白了。
我添加了一个文本框,他们可以在其中键入所需的逻辑。
我一直在绞尽脑汁,我对如何做到这一点感到困惑。 我唯一的结论是能够以某种方式创建一个基于他们在文本框中键入的文本的动态 IF 语句,但我不知道这是否可能。
似乎应该有一个简单的方法可以做到这一点,但我无法弄清楚。 如果有人能帮助我,我将不胜感激。
谢谢!
这是一个完整的测试,可以根据需要使用正则表达式和 AND、OR 和括号。请注意,这仅支持运算符AND
和OR
以及括号(
和)
,并且期望输入的格式正确(正则表达式不得包含空格(。解析可以改进,想法保持不变。
以下是整体测试:
var input = ".* AND [0-9]+ AND abc OR (abc AND def)";
var rpn = ParseRPN(input);
var test = GetExpression(new Queue<string>(rpn.Reverse())).Compile();
test("abc"); // false
test("abc0"); // true
test("abcdef"); // true
以下是反向抛光表示法的解析:
public Queue<string> ParseRPN(string input)
{
// improve the parsing into tokens here
var output = new Queue<string>();
var ops = new Stack<string>();
input = input.Replace("(","( ").Replace(")"," )");
var split = input.Split(' ');
foreach (var token in split)
{
if (token == "AND" || token == "OR")
{
while (ops.Count > 0 && (ops.Peek() == "AND" || ops.Peek() == "OR"))
{
output.Enqueue(ops.Pop());
}
ops.Push(token);
}
else if (token == "(") ops.Push(token);
else if (token == ")")
{
while (ops.Count > 0 && ops.Peek() != "(")
{
output.Enqueue(ops.Pop());
}
ops.Pop();
}
else output.Enqueue(token); // it's a number
}
while (ops.Count > 0)
{
output.Enqueue(ops.Pop());
}
return output;
}
神奇的GetExpression
:
public Expression<Func<string,bool>> GetExpression(Queue<string> input)
{
var exp = input.Dequeue();
if (exp == "AND") return GetExpression(input).And(GetExpression(input));
else if (exp == "OR") return GetExpression(input).Or(GetExpression(input));
else return (test => Regex.IsMatch(test, exp));
}
请注意,这确实依赖于PredicateBuilder
,但使用的扩展函数在这里是完整的:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T> () { return f => true; }
public static Expression<Func<T, bool>> False<T> () { return f => false; }
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}
}
如下所示 - 定义操作类来表示二进制操作并构建树:
interface IFilter
{
bool Filter(Record r);
}
class SimpleFilter : IFilter
{
bool Filter(Record r)
{
return RegExpMatch(r);
}
}
class AndFilter : IFilter
{
public AndFilter(IFilter left, IFilter right) {}
bool Filter(Record r)
{
return left.Filter(r) && right.Filter(r);
}
}
class OrFilter : IFilter
{
public OrFilter(IFilter left, IFilter right) {}
bool Filter(Record r)
{
return left.Filter(r) || right.Filter(r);
}
}
第一步是将表达式解析为抽象语法树。为此,您可以使用调车场算法。
完成此操作后,您可以使用虚拟方法或接口递归评估树。例如,您可以有一个 SimpleNode
类,该类表示一个简单的表达式(如示例中的表达式1
(,并且可以对其进行计算。然后,您有表示AND
操作并具有两个子节点的AndNode
。它评估子节点并返回两个节点是否成功。
规范模式的解释(带有示例代码(应该会有所帮助。
http://en.wikipedia.org/wiki/Specification_pattern#C.23
可能有库可以为您做这种事情,但在过去,我基于使用 Predicate 沿着这些思路手工滚动了一些东西; 使用系统; 使用 System.Collections.Generic; 使用System.Linq; 使用系统文本; 使用 System.Text.RegularExpressions;
namespace ConsoleApplication1
{
public enum CombineOptions
{
And,
Or,
}
public class FilterExpression
{
public string Filter { get; set; }
public CombineOptions Options { get; private set; }
public FilterExpression(string filter, CombineOptions options)
{
this.Filter = filter;
this.Options = options;
}
}
public static class PredicateExtensions
{
public static Predicate<T> And<T>
(this Predicate<T> original, Predicate<T> newPredicate)
{
return t => original(t) && newPredicate(t);
}
public static Predicate<T> Or<T>
(this Predicate<T> original, Predicate<T> newPredicate)
{
return t => original(t) || newPredicate(t);
}
}
public static class ExpressionBuilder
{
public static Predicate<string> BuildExpression(IEnumerable<FilterExpression> filterExpressions)
{
Predicate<string> predicate = (delegate
{
return true;
});
foreach (FilterExpression expression in filterExpressions)
{
string nextFilter = expression.Filter;
Predicate<string> nextPredicate = (o => Regex.Match(o, nextFilter).Success);
switch (expression.Options)
{
case CombineOptions.And:
predicate = predicate.And(nextPredicate);
break;
case CombineOptions.Or:
predicate = predicate.Or(nextPredicate);
break;
}
}
return predicate;
}
}
class Program
{
static void Main(string[] args)
{
FilterExpression f1 = new FilterExpression(@"data([A-Za-z0-9'-]+)$", CombineOptions.And);
FilterExpression f2 = new FilterExpression(@"otherdata([A-Za-z0-9'-]+)$", CombineOptions.And);
FilterExpression f3 = new FilterExpression(@"otherdata([A-Za-z0-9'-]+)$", CombineOptions.Or);
// result will be false as "data1" does not match both filters
Predicate<string> pred2 = ExpressionBuilder.BuildExpression(new[] { f1, f2 });
bool result = pred2.Invoke("data1");
// result will be true as "data1" matches 1 of the 2 Or'd filters
Predicate<string> pred3 = ExpressionBuilder.BuildExpression(new[] { f1, f3 });
result = pred3.Invoke("data1");
}
}
}
您现在需要做的就是解析"括号",以确定将 FilterExpressions 发送到 BuildExpression 方法的顺序。您可能需要针对更复杂的表达式对其进行调整,但希望这会有所帮助。