函数数组的逻辑运算符

时间:2014-03-17 15:12:11

标签: c# .net arrays lambda logic

我有一段时间有这个想法,我很好奇是否有可能以有价值的方式实施。我想获取一个布尔返回的lambda表达式数组,并对其结果执行逻辑运算。这是一个有效列表的无意义的例子:

var tests = new List<Func<int, bool>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

我想做的事实上是

bool result = tests.And();

或执行其他逻辑操作。我意识到我可以写一个IEnumerable实现类并执行此操作,但我想知道它是否已经完成。显然,实现必须以与

相同的方式有效地工作,短路
if (truthyExpression || falseyExpression)

永远无法评估falseyExpression

我在框架中看到的唯一可能有帮助的是Bit Array,但我不确定如何在不预先评估每个表达式的情况下使用它,从而破坏了短篇小说的有用性

4 个答案:

答案 0 :(得分:5)

您可以使用内置的Enumerable.AnyEnumerable.All扩展程序。

bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));

答案 1 :(得分:2)

当然可以。

简单方法

var tests = new List<Func<int, bool>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

我们将通过逐步逻辑地将所有这些谓词聚合为一个,并将每个谓词与已存在的结果进行汇总。既然我们需要从某个地方开始,我们将从x => true开始,因为该谓词在执行AND时是中性的(如果你是OR,则从x => false开始):

var seed = (Func<int, bool>)(x => true);
var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Func<int, bool>)(x => combined(x) && expr(x)));

Console.WriteLine(allTogether.Invoke(30)); // True

这很简单!它确实有一些限制:

  • 它只适用于对象(就像你的例子一样)
  • 当你的谓词列表变大(所有这些函数调用)时,它可能会有点效率低下。

困难的方法(使用表达式树而不是编译的lambdas)

这可以在任何地方使用(例如,您也可以使用它将谓词传递给SQL提供程序,例如Entity Framework),并且在任何情况下它也会提供更“紧凑”的最终结果。但要让它发挥作用要困难得多。我们来吧。

首先,将输入更改为表达式树。这很简单,因为编译器会为您完成所有工作:

var tests = new List<Expression<Func<int, bool>>>() {
    (x) => x > 10,
    (x) => x < 100,
    (x) => x != 42
};

然后将这些表达式的主体聚合成一个,与以前相同的想法。不幸的是,这是微不足道的,一直在工作,但请耐心等待:

var seed = (Expression<Func<int, bool>>)
    Expression.Lambda(Expression.Constant(true), 
    Expression.Parameter(typeof(int), "x"));

var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Expression<Func<int, bool>>)
        Expression.Lambda(
        Expression.And(combined.Body, expr.Body), 
        expr.Parameters
    ));

现在我们在这里做的是从所有单个谓词构建一个巨大的BinaryExpression表达式。

您现在可以将结果传递给EF或告诉编译器将其转换为代码并运行它,并且您可以免费获得短路:

Console.WriteLine(allTogether.Compile().Invoke(30)); // should be "true"

不幸的是,最后一步不会因为深奥的技术原因而起作用。

但为什么它不起作用?

因为allTogether表示的表达式树有点像这样:

FUNCTION 
  PARAMETERS: PARAM(x)
  BODY:  AND +-- NOT-EQUAL +---> PARAM(x)
          |             \---> CONSTANT(42)
          |
         AND +-- LESS-THAN +---> PARAM(x)
             |             \---> CONSTANT(100)
             |
            AND +-- GREATER-THAN +---> PARAM(x)
             |                   \---> CONSTANT(10)
             |
            TRUE

上面树中的每个节点代表要编译的表达式树中的Expression对象。问题是这些PARAM(x)节点中的所有4个节点在逻辑上相同,实际上是不同的实例(这有助于编译器通过自动创建表达式树来提供给我们?好吧,每个节点自然都有它们自己的参数实例),而对于工作的最终结果,它们必须是同一个实例。我知道这是因为it has bitten me in the past

所以,这里需要做的是,然后迭代生成的表达式树,找到ParameterExpression的每个匹配项,并用相同的实例替换它们中的每一个。同一个实例也是构造seed时使用的第二个参数。

显示如何做到这一点将使这个答案的方式比它有权利的时间更长,但无论如何我们都要这样做。我不打算发表评论,你应该认识到这里发生了什么:

class Visitor : ExpressionVisitor
{
    private Expression param;

    public Visitor(Expression param)
    {
        this.param = param;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return param;
    }
}

然后:

var param = Expression.Parameter(typeof(int), "x");
var seed = (Expression<Func<int, bool>>)
    Expression.Lambda(Expression.Constant(true), 
    param);
var visitor = new Visitor(param);

var allTogether = tests.Aggregate(
    seed,
    (combined, expr) => (Expression<Func<int, bool>>)
        Expression.Lambda(
        Expression.And(combined.Body, expr.Body), 
        param
    ),
    lambda => (Expression<Func<int, bool>>)
        // replacing all ParameterExpressions with same instance happens here
        Expression.Lambda(visitor.Visit(lambda.Body), param)
    );

Console.WriteLine(allTogether.Compile().Invoke(30)); // "True" -- works! 

答案 2 :(得分:2)

为了将Func<int, bool>个对象的序列转换为bool,您需要有一个整数来应用于每个值。如果您已经知道该整数是什么,那么您可以执行Julien describes

bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));

如果你不这样做,那么你想要做的是从布尔序列中创建一个Func<int, bool>,而不是bool

Func<int, bool> andResult = value => tests.All(test => test(value));
Func<int, bool> orResult = value => tests.Any(test => test(value));

我们可以很容易地将其概括为通用函数:

public static Func<T, bool> And<T>(this IEnumerable<Func<T, bool>> predicates)
{
    return value => predicates.All(p => p(value));
}
public static Func<T, bool> Or<T>(this IEnumerable<Func<T, bool>> predicates)
{
    return value => predicates.Any(p => p(value));
}

允许你写:

Func<int, bool> result = tests.And();

答案 3 :(得分:0)

这个怎么样?

using System;
using System.Collections.Generic;
using System.Linq;
namespace SO7
{
    class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            LogicList<int> intBasedLogicalList = new LogicList<int>(new Func<int, bool>[] {x => x<3, x => x <5, x => x<8});
            Console.WriteLine(intBasedLogicalList.And(2));
            Console.WriteLine(intBasedLogicalList.And(4));
            Console.WriteLine(intBasedLogicalList.Or(7));
            Console.WriteLine(intBasedLogicalList.Or(8));

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }

    public class LogicList<T> : List<Func<T, bool>>
    {

        private List<Func<T,bool>> _tests;

        public LogicList(IEnumerable<Func<T, bool>> tests)
        {
            _tests = new List<Func<T, bool>>();
            foreach(var test in tests)
            {
                _tests.Add(test);
            }
        }

        public bool And(T argument){
            foreach(var test in _tests)
            {
                if (!test(argument)){
                    return false;
                }
            }
            return true;
        }

        public bool Or(T argument){
            return _tests.Any(x => x(argument));

        }

    }

}