【问题标题】:C# Expression ComparisonC# 表达式比较
【发布时间】:2015-04-18 19:08:06
【问题描述】:

假设我在一个集合上有以下表达式:

var people = new List<Person>
{
     new Person {FullName = "Some Dude", Age = 45},
     new Person {FullName = "Another Dude", Age = 28},
     new Person {FullName = "Some Other Dude", Age = 36}
 };

var filtered = people.Where(person => person.Age > 28 && person.FullName.StartsWith("So"));
var narrowlyFiltered = people.Where(person => person.Age > 36 && person.FullName.StartsWith("Some"));

有没有办法比较这两个表达式并推断出第二个表达式是运行时第一个表达式的子集?无需枚举或其他任何内容。我只有表达式,我试图找出这些表达式是否相交或包含彼此。

【问题讨论】:

  • 你的意思是结果,还是所有情况?
  • 可以,只要不让Expression变成委托即可。
  • 没有相当复杂的方法可以做到这一点,不。但是,不难检测其中一个的结果是否是另一个的子集。
  • 是的,有办法。但这需要做一些事情。你确定你需要这样做吗?难道没有办法在它被放入表达式之前做到这一点吗?
  • @CommuSoft 和乔纳森认为我还没有列举它。我只有两个空间的定义,我试图找出一个空间是否包含另一个空间。

标签: c# lambda


【解决方案1】:

如果您可以枚举您的集合,您可以先将元素放入HashSet&lt;T&gt;,然后在其上运行HashSet&lt;T&gt;.IsSubSet

HashSet<T> hs = new HashSet<T>(filtered);
HashSet<T> hs2 = new HashSet<T>(narrowlyFiltered);
hs.IsSubSetOf(hs2); //<- booleans saying true or false

否则,这个问题一般是undecidable problem。尽管有一些启发式方法可以适用于 许多 种情况。例如,您可以尝试使用旨在在编译时推断出这一点的代码协定。

证明:

正式的变体是:给定两个图灵机(方法、委托、指针),第一种语言中包含的每个字符串是否都包含在第二种语言中?
不可判定
证明:假设它是可判定的,EQTM 将是可判定的:只需首先验证第一个图灵机是否是第二个图灵机的子集,反之亦然。如果两者都是子集,我们知道它们接受相同的字符串集。

换句话说,如果你能做到这一点,你也可以推断出两个函数是否产生相同的结果,which cannot be implemented

【讨论】:

  • 不要说“不可判定”之类的消极的话 :) 无论如何,感谢您花时间解释为什么不能这样做。
  • @EmreSenturk:在每本关于可计算性的教科书中,都使用了“不可判定”一词。不幸的是,它既不是“半可判定的”:(
【解决方案2】:

您必须将每个 Expression 分解为所有可能的继承类型(MethodCallExpression、ConditionalExpression 等),然后并行执行每个分解并检查每个可能的参数...编写代码会有点长...您可以通过ExpressionEqualityComparer 启发自己

【讨论】:

  • 谢谢!我试图解释的很像这个。
  • @Sharped:您提出的算法确定两个表达式“literally”是否相同。但是,如果您将两个表达式之一封装在一个方法中,它将失败。
  • @Sharped:从表达式内部调用中出现循环的那一刻起,问题就开始出现了。这里所说的问题是Rice's theorem 的一个很好的例子。
  • person.Age => MemberExpression 在这两种情况下,针对相同类型的相同属性。 person.Age &gt; 28person.Age &gt; 36 都是具有相同左操作数的 BinaryExpression...等等...有很多共同点。
  • 我所说的子集是指两个表达式树之间的公共节点的一小部分,在“几乎相等”的比较中(如果它们是相同的类型,您可以认为两个节点几乎相等,但没有完全相同的参数)。它与严格的 B-Tree 代数无关。有了这个“几乎相等”的概念,是的,我们可以说交集
【解决方案3】:

看看Specification design pattern

一旦它被实现,那么在这种情况下你的规范就变成了

public class PersonNamedOlderThanSpecification : CompositeSpecification<Person>
{
    private string name;
    private int age;

    public PersonNamedOlderThanSpecification(string name, int age)
    {
        this.name = name;
        this.age = age;
    }


    public override bool IsSatisfiedBy(Person entity)
    {
        return (entity.Name.Contains(this.name)) && (entity.Age > age);
    }
}

那么你可以这样使用它:

var personSpecs = new PersonNamedOlderThanSpecification("So", 28);
var personSpecs2 = new PersonNamedOlderThanSpecification("Some", 36);

var filtered = people.FindAll(x => personSpecs.IsSatisfiedBy(x));
var adjusted = people.FindAll(x => personSpecs2.IsSatisfiedBy(x));

【讨论】:

  • 我认为这并不能真正提供问题的答案。虽然您确实可以在规范中实现IsSubSetOf 逻辑....
  • 我在列表中使用此模式进行所有过滤,因为值是动态的,结果列表是满足规范的所有实体
  • 但是你是对的我误解了这个问题,专注于过滤的部分
  • 当我创建它时没有想到这样做,我会试一试,谢谢... sry 帖子没有帮助您解决问题... 阅读问题两次,我应该嗯
  • @CheGueVerra 不用担心 :) 我是那个没有明确说明问题的人。
【解决方案4】:

你可以试试这个:

var people = new List<Person>
{
     new Person {FullName = "Some Dude", Age = 45},
     new Person {FullName = "Another Dude", Age = 28},
     new Person {FullName = "Some Other Dude", Age = 36}
};

var filtered = people.Where(person => person.Age > 28 && person.FullName.StartsWith("So"));
var narrowlyFiltered = people.Where(person => person.Age > 36 && person.FullName.StartsWith("Some"));

var intersection = filtered.Intersect(narrowlyFiltered);
if (intersection != null)
{
    if (intersection.Count() > 0)
    {
        //narrowlyFiltered is subset of filtered
    }
}

【讨论】:

  • 是的,但它确实列举了它。
  • 好的,如果你只想知道narrowlyFiltered是否是filtered的子集(真或假),在这个post中,解释解决方案;在你的情况下,你这样做: bool subset = filtered.Any(v => narrowlyFiltered.Any());
【解决方案5】:

这完全取决于您如何衡量相等的权重,比较表达式时更重要的是什么等。 例如,如果您有完全不同的过滤器,那么您在实际执行之前可能不会知道查询差异。

要完全控制您的比较,请创建一个具有一些可用于过滤的属性的过滤器类,然后构建表达式并使用此类而不是使用访问者进行比较。 您可以准备通用函数来比较整数、整数对(用于范围)等。

我没有检查下面的代码,但它应该是一个好的开始。

public class PersonFilter:  IComparable<PersonFilter>
{
    public int? MinAge { get; set; }

    public int? MaxAge { get; set; }

    public string NamePrefix { get; set; }

    public Expression<Predicate<Person>> Filter
    {
        return people => people.Where(person => (!MinAge.HasValue || person.Age > MinAge.Value) && 
            (!MaxAge.HasValue || person.Age < MaxAge.Value) && 
            (string.IsNullOrEmpty(NamePrefix) || person.FullName.StartsWith(NamePrefix))
    }

    // -1 if this filter is filtering more than the other
    public int CompareTo(PersonFilter other)
    {
        var balance = 0; // equal
        if(MinAge.HasValue != other.MinAge.HasValue)
        {
            balance += MinAge.HasValue ? -1 : 1;
        }
        else if(MinAge.HasValue)
        {
            balance += MinAge.Value.CompareTo(other.MinAge.Value) ?
        }
        if(string.IsNullOrEmpty(NamePrefix) != string.IsNullOrEmpty(other.NamePrefix))
        {
            balance += string.IsNullOrEmpty(NamePrefix) ? -1 : 1;
        }
        else if(!string.IsNullOrEmpty(NamePrefix))
        {
            if(NamePrefix.StartsWith(other.NamePrefix))
            {
                balance -= 1;
            }
            else if(other.NamePrefix.StartsWith(NamePrefix))
            {
                balance += 1;
            }
            else
            {
                // if NamePrefix is the same or completely different let's assume both filters are equal
            }
        }
        return balance;
    }

    public bool IsSubsetOf(PersonFilter other)
    {
        if(MinAge.HasValue != other.MinAge.HasValue)
        {
            if(other.MinAge.HasValue)
            {
                return false;
            }
        }
        else if(MinAge.HasValue && MinAge.Value < other.MinAge.Value)
        {
            return false;
        }

        if(string.IsNullOrEmpty(NamePrefix) != string.IsNullOrEmpty(other.NamePrefix))
        {
            if(!string.IsNullOrEmpty(other.NamePrefix))
            {
                return false;
            }
        }
        else if(!string.IsNullOrEmpty(NamePrefix))
        {
            if(!NamePrefix.StartsWith(other.NamePrefix))
            {
            return false;
            }
        }

        return true;
    }
}

【讨论】:

  • 谢谢!我决定使用一个表达式的过滤器值创建一个虚拟 Person 实例,并将其放入集合和 eval 中。此集合的第二个表达式,但您的建议似乎更明确。
  • 为什么首先需要比较表达式?我发现很难在这种比较中找到商业价值,除非您想检查是否需要另一个查询,或者假设您已经有了更广泛查询的结果。在上述代码中的这种情况下,您需要更改余额计算,以便任何需要新查询的差异都会立即返回 1。
  • 将此视为一种在完美分片的数据库中查找要查询的数据库的方法:)
  • 在这种情况下,您肯定会想出足够的自定义比较。祝你好运:)
  • @too:与天平的比较有点问题。您根本无法在这些有意义的实例上定义完整的订单。你应该定义一个偏序,你可以说它等于、大于或小于,或者你不能说它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-21
相关资源
最近更新 更多