【问题标题】:Logical operator on array of functions函数数组的逻辑运算符
【发布时间】:2014-03-17 15:12:10
【问题描述】:

我有这个想法已经有一段时间了,我很好奇它是否有可能以一种有价值的方式实施。我想获取一组返回布尔值的 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,但我不确定如何在不预先评估每个表达式的情况下使用它,从而破坏短路的用处。

【问题讨论】:

  • 您能提供一些示例输入和输出吗?我有点不清楚您希望输出是什么。
  • 听起来你唯一的问题是“这已经完成了吗”,这不是一个很好的问题。如果还没有完成,那么创建一个基于IEnumerable 的实现是可行的方法。您不确定该怎么做?如果是这样,请告诉我们哪些问题让您感到困惑。否则,您似乎知道足以自己解决这个问题。
  • @siride,我问这个问题不是因为我不知道 a 方法,而是因为我不知道是否有 an 接受方式,甚至是一个很好的方法。我从答案中学到了新东西,我想其他人可能会偶然发现这一点并从中受益,所以我认为这个问题在这里占有一席之地。以后我会尽量说得更清楚、更具体。
  • @Andrew:不幸的是,无论好坏,这个网站上的普遍观点是,征求意见或讨论哪种方式更好或更坏的问题不是好问题。

标签: c# .net arrays lambda logic


【解决方案1】:

您可以使用内置的Enumerable.AnyEnumerable.All 扩展方法。

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

【讨论】:

  • @khillang Enumerable.Any 如果委托返回 true 将“短路”(并返回 true 本身),如果委托返回 Enumerable.All 将“短路”false (并返回 false 本身)。
  • 糟糕,我的意思是相反的:P
【解决方案2】:

当然可以。

简单的方法

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

我们将通过将每个谓词与已经存在的结果进行渐进式逻辑与运算,将所有这些谓词聚合为一个。由于我们需要从某个地方开始,我们将从x =&gt; true 开始,因为该谓词在执行AND 时是中性的(如果您是OR,则以x =&gt; 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

这很容易!但它确实有一些限制:

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

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

这将适用于任何地方(例如,您也可以使用它将谓词传递给 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对象。问题是所有这 4 个 PARAM(x) 节点,虽然逻辑上相同,但实际上是 不同的实例(这有助于编译器通过自动创建表达式树来帮助我们?嗯,每个自然都有自己的参数实例),而要使最终结果起作用它们必须是相同的实例。我知道这是因为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! 

【讨论】:

  • @siride:对不起,这个答案需要很长时间才能写出来,所以我会逐步进行。回来看导演剪辑版,我保证是值得的。
  • 只有当您需要表达式中的结果(例如,将其提供给数据库查询提供者)时,才真正值得处理表达式。如果不这样做,您可以简单地使用传统的委托组合来组合函数,这要容易得多。你只是太努力了。
  • @Servy:是的。问题没有说,我选择了一般情况。转念一想,这不是最好的主意。我会修改的。
  • 如果你真的想用Expression 对象来解决这个问题,尽管问题中没有任何迹象表明这是有道理的,但你想要的是PredicateBuilder 的实现。请参阅我的this answer 了解实现。
  • @Servy:好吧,一旦走上坎坷的道路,就没有回头路了。你当然有我的赞成票。
【解决方案3】:

为了将Func&lt;int, bool&gt; 对象序列转换为bool,您需要有一个整数来应用于每个值。如果你已经知道那个整数是什么,那么你可以做什么Julien describes

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

如果你不这样做,那么你想做的是从布尔序列创建一个Func&lt;int, bool&gt;,而不是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();

【讨论】:

    【解决方案4】:

    这个怎么样?

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-25
      • 1970-01-01
      • 1970-01-01
      • 2020-02-18
      • 2021-03-05
      • 2018-01-13
      • 1970-01-01
      • 2016-10-21
      相关资源
      最近更新 更多