比较多变量布尔函数

时间:2014-10-31 18:04:16

标签: c# c#-4.0 lambda

我正在编写的应用程序涉及将明文解析为布尔lambda表达式。 出于测试目的,我需要确保正确进行此解析。为此,我编写了一个递归表达式树比较例程bool ExpressionComparison.EquvalentTo(this Expression self, Expression other)。如果selfother都是逻辑运算符,并且它们共享同一组操作数,则EquvalentTo应返回true。

我当前的算法使用ExpressionVisitor。我将lambda表达式的主体传递给此访问者,它用布尔参数替换所有操作数(非逻辑运算符的表达式)。我在两个lambdas上调用它,比较操作数,重新排序第二个lambda的二进制参数,如果需要,所以对应于相同的操作数,编译两个二进制函数,并详尽地比较结果。

有没有更有效的方法来比较两个布尔函数的逻辑等价?

public static class ExpressionComparison
{
    public static bool Equals(this Expression left, Expression right)
    {
        return Compare(left, right, Equals);
    }

    public static bool EquvalentTo(this Expression self, Expression other)
    {
        if (Object.Equals(self, other))
            return true;

        if (self == null || other == null)
            return false;

        if ((self.NodeType == ExpressionType.Not || self.NodeType == ExpressionType.AndAlso || self.NodeType == ExpressionType.OrElse) &&
            (other.NodeType == ExpressionType.Not || other.NodeType == ExpressionType.AndAlso || other.NodeType == ExpressionType.OrElse))
            return new Visitor(self).Compare(new Visitor(other), EquvalentTo);
        else if (self.NodeType != other.NodeType)
            return false;

        if (self.NodeType == ExpressionType.Constant)
        {
            var _self = (ConstantExpression)self;
            var _other = (ConstantExpression)other;
            if (Object.ReferenceEquals(_self.Value, _other.Value))
                return true;
        }

        if (self.Type != other.Type)
            return false;

        switch (self.NodeType)
        {
            case ExpressionType.Add:
            case ExpressionType.AddChecked:
            case ExpressionType.Multiply:
            case ExpressionType.MultiplyChecked:

            case ExpressionType.And:
            case ExpressionType.Or:
            case ExpressionType.ExclusiveOr:

                var selfsubs = ((BinaryExpression)self).Linearize().ToList();
                var othersubs = ((BinaryExpression)other).Linearize().ToList();

                foreach (var leftsub in selfsubs)
                {
                    int i;
                    for (i = othersubs.Count - 1; i >= 0; i--)
                        if (EquvalentTo(leftsub, othersubs[i]))
                        {
                            othersubs.RemoveAt(i);
                            break;
                        }

                    if (i < 0)
                        return false;
                }

                return othersubs.Count == 0;
        }

        return Compare(self, other, EquvalentTo);
    }

    private static bool Compare(Expression self, Expression other, Func<Expression, Expression, bool> comparer)
    {
        if (Object.Equals(self, other))
            return true;

        if (self == null || other == null)
            return false;

        if (self.NodeType != other.NodeType)
            return false;

        if (self.Type != other.Type)
            return false;

        if (self is BinaryExpression)
        {
            var _left = (BinaryExpression)self;
            var _right = (BinaryExpression)other;
            return comparer(_left.Left, _right.Left) && comparer(_left.Right, _right.Right);
        }
        else if (self is ConditionalExpression)
        {
            var _left = (ConditionalExpression)self;
            var _right = (ConditionalExpression)other;
            return comparer(_left.IfFalse, _right.IfFalse) && comparer(_left.IfTrue, _right.IfTrue) && comparer(_left.Test, _right.Test);
        }
        else if (self is ConstantExpression)
        {
            var _left = (ConstantExpression)self;
            var _right = (ConstantExpression)other;
            if (!Object.Equals(_left.Value, _right.Value))
                return false;
            return true;
        }
        else if (self is DefaultExpression)
        {
            return true;
        }
        else if (self is IndexExpression)
        {
            var _left = (IndexExpression)self;
            var _right = (IndexExpression)other;
            if (_left.Arguments.Count != _right.Arguments.Count)
                return false;
            if (!Object.Equals(_left.Object, _right.Object))
                return false;
            if (!Object.Equals(_left.Indexer, _right.Indexer))
                return false;
            for (var i = _left.Arguments.Count - 1; i >= 0; i--)
                if (!comparer(_left.Arguments[i], _right.Arguments[i]))
                    return false;
            return true;
        }
        else if (self is InvocationExpression)
        {
            var _left = (InvocationExpression)self;
            var _right = (InvocationExpression)other;
            if (_left.Arguments.Count != _right.Arguments.Count)
                return false;
            if (!comparer(_left.Expression, _right.Expression))
                return false;
            for (var i = _left.Arguments.Count - 1; i >= 0; i--)
                if (!comparer(_left.Arguments[i], _right.Arguments[i]))
                    return false;
            return true;
        }
        else if (self is LambdaExpression)
        {
            var _left = (LambdaExpression)self;
            var _right = (LambdaExpression)other;
            if (_left.Parameters.Count != _right.Parameters.Count)
                return false;
            for (var i = _left.Parameters.Count - 1; i >= 0; i--)
                if (!comparer(_left.Parameters[i], _right.Parameters[i]))
                    return false;
            if (!Object.Equals(_left.ReturnType, _right.ReturnType))
                return false;
            return comparer(_left.Body, _right.Body);
        }
        else if (self is ListInitExpression)
        {
            var _left = (ListInitExpression)self;
            var _right = (ListInitExpression)other;
            if (comparer(_left.NewExpression, _right.NewExpression))
                return false;
            for (var i = _left.Initializers.Count - 1; i >= 0; i--)
            {
                var leftinit = _left.Initializers[i];
                var rightinit = _right.Initializers[i];
                if (Object.Equals(leftinit, rightinit))
                    continue;
                if (!Object.Equals(leftinit.AddMethod, rightinit.AddMethod))
                    return false;
                if (leftinit.Arguments.Count != rightinit.Arguments.Count)
                    return false;
                for (var j = leftinit.Arguments.Count - 1; j >= 0; j--)
                    if (!leftinit.Arguments[i].EquvalentTo(rightinit.Arguments[i]))
                        return false;
            }
            return true;
        }
        else if (self is MemberExpression)
        {
            var _left = (MemberExpression)self;
            var _right = (MemberExpression)other;
            if (!Object.Equals(_left.Member, _right.Member))
                return false;
            return comparer(_left.Expression, _right.Expression);
        }
        else if (self is MemberInitExpression)
        {

        }
        else if (self is MethodCallExpression)
        {
            var _left = (MethodCallExpression)self;
            var _right = (MethodCallExpression)other;
            if (!Object.Equals(_left.Method, _right.Method))
                return false;
            if (!comparer(_left.Object, _right.Object))
                return false;
            for (var i = _left.Arguments.Count - 1; i >= 0; i--)
                if (!comparer(_left.Arguments[i], _right.Arguments[i]))
                    return false;
            return true;
        }
        else if (self is NewArrayExpression)
        {
            var _left = (NewArrayExpression)self;
            var _right = (NewArrayExpression)other;
            for (var i = _left.Expressions.Count - 1; i >= 0; i--)
                if (!comparer(_left.Expressions[i], _right.Expressions[i]))
                    return false;
            return true;
        }
        else if (self is NewExpression)
        {
            var _left = (NewExpression)self;
            var _right = (NewExpression)other;
            if (!Object.Equals(_left.Constructor, _right.Constructor))
                return false;
            for (var i = _left.Members.Count - 1; i >= 0; i--)
                if (!Object.Equals(_left.Members[i], _right.Members[i]))
                    return false;
            for (var i = _left.Arguments.Count - 1; i >= 0; i--)
                if (!comparer(_left.Arguments[i], _right.Arguments[i]))
                    return false;
            return true;
        }
        else if (self is ParameterExpression)
        {
            return true;
        }
        else if (self is TypeBinaryExpression)
        {
            var _left = (TypeBinaryExpression)self;
            var _right = (TypeBinaryExpression)other;
            if (_left.TypeOperand != _right.TypeOperand)
                return false;
            return comparer(_left.Expression, _right.Expression);
        }
        else if (self is UnaryExpression)
        {
            var _left = (UnaryExpression)self;
            var _right = (UnaryExpression)other;
            if (!Object.Equals(_left.Method, _right.Method))
                return false;
            return comparer(_left.Operand, _right.Operand);
        }

        throw new Exception("Unhandled expression type: " + self.GetType());
    }

    private static IEnumerable<Expression> Linearize(this BinaryExpression expr)
    {
        var type = expr.NodeType;

        if (expr.Left.NodeType == type)
            foreach (Expression subexpr in ((BinaryExpression)expr.Left).Linearize())
                yield return subexpr;
        else
            yield return expr.Left;

        if (expr.Right.NodeType == type)
            foreach (Expression subexpr in ((BinaryExpression)expr.Right).Linearize())
                yield return subexpr;
        else
            yield return expr.Right;
    }

    class Visitor : ExpressionVisitor
    {
        IList<Expression> _expressions = new List<Expression>();
        IList<ParameterExpression> _parameters = new List<ParameterExpression>();

        ParameterExpression _next(Expression expression)
        {
            var parameter = Expression.Parameter(typeof(Boolean));

            _expressions.Add(expression);
            _parameters.Add(parameter);

            return parameter;
        }

        public override Expression Visit(Expression node)
        {
            switch (node.NodeType)
            {
                case ExpressionType.Not:
                case ExpressionType.AndAlso:
                case ExpressionType.OrElse:
                    return base.Visit(node);

                default:
                    return _next(node);
            }
        }

        public Expression Visited { get; private set; }
        public Expression[] Expressions { get; private set; }
        public ParameterExpression[] Parameters { get; private set; }

        public Visitor(Expression node)
        {
            Visited = Visit(node);
            Expressions = _expressions.ToArray();
            Parameters = _parameters.ToArray();
        }

        private Delegate NaturalOrder()
        {
            return Expression.Lambda(Visited, Parameters).Compile();
        }

        private Delegate ForcedOrder(int[] order)
        {
            return Expression.Lambda(Visited, order.Select(i => Parameters[i])).Compile();
        }

        public bool Compare(Visitor that, Func<Expression, Expression, bool> comparer)
        {
            if (this.Expressions.Length != that.Expressions.Length)
                return false;

            var order = new int[this.Expressions.Length];
            var CompareExpr = that.Expressions.ToArray();
            for (int i = Expressions.Length - 1, j; i >= 0; i--)
                if (comparer(Expressions[i], CompareExpr[i]))
                    order[i] = i;
                else
                {
                    for (j = CompareExpr.Length - 1; j >= 0; j--)
                        if (i == j)
                            continue;
                        else if (comparer(Expressions[i], CompareExpr[j]))
                        {
                            order[i] = j;
                            CompareExpr[j] = null;
                            break;
                        }
                    if (j < 0)
                        return false;
                }

            dynamic thisd = this.NaturalOrder();
            dynamic thatd = that.ForcedOrder(order);
            var args = new bool[order.Length];
            for (var i = ((ulong)1 << order.Length); i > 0; i--)
            {
                for (var j = (byte)order.Length - 1; j >= 0; j--)
                    args[j] = ((i - 1) & ((ulong)1 << j)) != 0;
                if (!FuncHelper.Exec(thisd, thatd, args))
                    return false;
            }

            return true;
        }
    }
}

Asside / Update:我正在将我在LINQ的WHERE子句中使用的Expression<Func<TModel, bool>>与实体进行比较。我想知道给定的文本字符串正确地解析为lambda,为此,我将生成的lambda与我期望的lambda进行比较。 我想知道两个lambda都会返回相同的结果集。我实际上无法对数据进行测试,因为有些查询是故意荒谬的,直到确保我的解析器是健壮的。此外,数据正在发生变化,查询A可能与查询B略有不同,但目前它们都返回相同的集合,等等。所以我测试了lambdas的等价性。

我将以下两种表达式定义为等效:

  • Object.Equals(a, b)返回true
  • ab 在逻辑上等效(仅适用于NotAndAlsoOrElse个节点;定义如下)
  • ab 可交换等效(仅适用于AddMultiply,(按位)And,{ {1}}和Or个节点;定义如下)
  • 他们ExclusiveOr的{​​{1}}和ExpressionType完全相同,所有操作数,运算符,参数,参数等等于(对于表达式)或等于(对于其他对象) )。

我将两个表达式定义为逻辑等效,如果:它们的'operands'是等价的,如果我用布尔参数替换每个布尔操作数,那么两个结果的n-ary布尔函数具有相同的真值表。过程如下:

  • 对于每个,请访问表达式,跳过NodeType abNot的任何节点,用新的布尔参数替换其他节点。布尔参数和它们替换的表达式在数组中捕获。
  • 检查是否捕获了相同数量的表达式。
  • 比较a到b的捕获表达式,确定它们是否是等价的集合,并确定相对顺序(AndAlso被认为等同于OrElse,但在更复杂的情况下,不同的顺序参数可能导致不同的真值表。)
  • 改变b的布尔参数的顺序,因此paramA.PropA == 1 && paramB.PropB == 2对应于某个表达式paramB.PropB == 2 && paramA.PropA == 1,它本身等同于b.Parameters[i]
  • 将访问过的表达式编译成两个布尔函数并比较它们的真值表。

我将两个表达式定义为交换等效,如果: b.Expressions[j] a.Expressions[i]ExpressionType的{​​{1}}完全相等,对于a和b,我是向下走到树上,停在与a / b不同a的任何b上,并将该表达式放在列表中,Expression列表和{{1}列表}包含同一组ExpressionType(由等效性确定)。

0 个答案:

没有答案