【问题标题】:Dynamic If Statement - Complex Filtering动态 If 语句 - 复杂过滤
【发布时间】:2012-05-01 01:20:01
【问题描述】:

我有一个 C# 项目,它允许用户使用正则表达式对数据创建过滤器。他们可以根据需要添加任意数量的过滤器。每个过滤器都包含一个字段和一个用户输入的正则表达式。

现在它适用于所有 AND 逻辑。我遍历每个过滤器,如果不匹配,我设置 skip = true 并跳出循环。然后,如果 skip == true 我跳过该记录并且不包含它。因此,每个过滤器都必须匹配才能包含该字段。

但是,现在他们希望能够添加更复杂的逻辑规则。例如,如果他们创建了 4 个过滤规则。他们希望能够指定: 1 与 2 与(3 或 4) 或者他们可能想要指定 1 或 2 或 3 或 4 或者他们可能想要指定 (1 和 2 和 3) 或 4 等等……我想你明白了。

我添加了一个文本框,他们可以在其中输入他们想要的逻辑。

我一直在绞尽脑汁,不知道如何完成这项工作。我唯一的结论是能够以某种方式创建一个基于他们在文本框中键入的文本的动态 IF 语句,但我不知道这是否可能。

似乎应该有一个简单的方法来做到这一点,但我想不通。如果有人可以帮助我,我将不胜感激。

谢谢!

【问题讨论】:

标签: c# if-statement filter


【解决方案1】:

这是一个完整的测试,可以根据需要使用正则表达式和 AND、OR 和括号。请注意,这只支持运算符ANDOR 以及括号(),并希望输入格式正确(正则表达式不能有空格)。解析可以改进,思路不变。

这是整体测试:

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);
  }
}

【讨论】:

    【解决方案2】:

    类似于以下内容 - 定义操作类来表示二元操作并构建您的树:

    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); 
     }
    }
    

    【讨论】:

      【解决方案3】:

      第一步是将表达式解析为抽象语法树。为此,您可以使用shunting-yard algorithm

      完成此操作后,您可以递归地评估树,使用虚拟方法或接口。例如,您可以有一个 SimpleNode 类,它表示一个简单的表达式(如您的示例中的 1)并可以对其进行评估。然后你有AndNode 代表AND 操作并且有两个子节点。它评估子节点并返回是否都成功。

      【讨论】:

        【解决方案4】:

        规范模式的这种解释(带有示例代码)应该会有所帮助。

        http://en.wikipedia.org/wiki/Specification_pattern#C.23

        【讨论】:

          【解决方案5】:

          可能有一些库可以为您做这种事情,但在过去,我已经根据使用 Predicate 手动完成了这些工作; 使用系统; 使用 System.Collections.Generic; 使用 System.Linq; 使用 System.Text; 使用 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 方法的顺序。您可能需要针对更复杂的表达式对其进行调整,但希望这会有所帮助。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-06
            • 2017-05-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多