【问题标题】:How to evaluate a complex expression tree against incremental data?如何针对增量数据评估复杂的表达式树?
【发布时间】:2013-10-23 17:38:19
【问题描述】:

我有一组数据和一组要针对这些数据运行的搜索过滤器。过滤器遵循 LDAP 搜索过滤器格式并被解析为表达式树。数据一次读取一项,并通过所有过滤器进行处理。中间匹配结果存储在树的每个叶节点中,直到处理完所有数据。然后通过遍历树并将逻辑运算符应用于每个叶子节点的中间结果来获得最终结果。例如,如果我有过滤器(&(a=b)(c=d)),那么我的树将如下所示:

root = "&"
    left = "a=b"
    right = "c=d"

所以如果a=bc=d 则左右子节点都是匹配的,因此过滤器是匹配的。

数据是不同类型对象的集合,每个对象都有自己的字段。例如,假设集合代表学校的一个班级:

class { name = "math" room = "12A" }
teacher { name = "John" age = "35" }
student { name = "Billy" age = "6" grade = "A" }
student { name = "Jane" age = "7" grade = "B" }

所以过滤器可能看起来像 (&(teacher.name=John)(student.age>6)(student.grade=A)) 并被解析为:

root = "&"
    left = "teacher.name=John"
    right = "&"
        left = "student.age>6"
        right = "student.grade=A"

我针对它运行class 对象;无匹配。我针对它运行teacher 对象; root.left 是匹配项。我针对它运行第一个 student 节点; root.right.right 是匹配项。我针对它运行第二个student 节点; root.right.left 是匹配项。然后我遍历树并确定所有节点都匹配,因此最终结果是匹配。

问题是中间匹配需要基于共性进行约束:student.agestudent.grade 过滤器需要以某种方式绑定在一起,以便仅当它们匹配同一个对象时才存储中间匹配。我这辈子都不知道该怎么做。

我的过滤器节点抽象基类:

class FilterNode
{
public:
    virtual void Evaluate(string ObjectName, map<string, string> Attributes) = 0;
    virtual bool IsMatch() = 0;
};

我有一个LogicalFilterNode 类来处理逻辑AND、OR 和NOT 操作;它的实现非常简单:

void LogicalFilterNode::Evaluate(string ObjectName, map<string, string> Attributes)
{
    m_Left->Evaluate(ObjectName, Attributes);
    m_Right->Evaluate(ObjectName, Attributes);
}

bool LogicalFilterNode::IsMatch()
{
    switch(m_Operator)
    {
    case AND:
        return m_Left->IsMatch() && m_Right->IsMatch();
    case OR:
        return m_Left->IsMatch() || m_Right->IsMatch();
    case NOT:
        return !m_Left->IsMatch();
    }
    return false;
}

然后我有一个处理叶节点的ComparisonFilterNode 类:

void ComparisonFilterNode::Evaluate(string ObjectName, map<string, string> Attributes)
{
    if(ObjectName == m_ObjectName) // e.g. "teacher", "student", etc.
    {
        foreach(string_pair Attribute in Attributes)
        {
            Evaluate(Attribute.Name, Attribute.Value);
        }
    }
}

void ComparisonFilterNode::Evaluate(string AttributeName, string AttributeValue)
{
    if(AttributeName == m_AttributeName) // e.g. "age", "grade", etc.
    {
        if(Compare(AttributeValue, m_AttributeValue) // e.g. "6", "A", etc.
        {
            m_IsMatch = true;
        }
    }
}

bool ComparisonFilterNode::IsMatch() { return m_IsMatch; }

如何使用:

FilterNode* Root = Parse(...);
foreach(Object item in Data)
{
    Root->Evaluate(item.Name, item.Attributes);
}
bool Match = Root->IsMatch();

基本上我需要的是子级具有相同对象名称的 AND 语句,只有当子级与同一对象匹配时,AND 语句才应匹配。

【问题讨论】:

  • 到目前为止,我只阅读了“问题是中间匹配需要根据共性进行限制”,但在我看来,您并没有按照您所说的句子 #3 , “数据一次读取一项并通过所有过滤器进行处理”。就这样做吧!您想测试集合的每个元素,对吗?在这种情况下,您不希望或不需要在元素之间保留任何状态!您需要一个最外层的循环,该循环遍历集合中的每个项目,将整个表达式树应用于每个项目,并得到一个是或否的答案来说明是否保留该项目。
  • 没有。过滤器可以包含多种元素的表达式。我一次只读取一个元素,可以是teacherstudent。像teacher.name=John AND student.grade=A 这样的过滤器永远不会匹配单个元素。
  • 如果过滤器可以包含多种元素的表达式,那么我不明白你在做什么。例如。你怎么知道什么老师对应什么孩子? “过滤”通常意味着“选择项目的子集”,其中每个项目都独立于其他项目来考虑。
  • 教师和学生是一个不好的例子,因为实际的数据元素彼此不相关。不同类型零件的库存将是一个更好的例子。过滤器可能也是错误的词。我想确定数据集中是否存在一组符合条件的元素。
  • 啊,我想我明白你现在想要做什么了。我会将我的建议放在答案中。

标签: algorithm expression-trees evaluation


【解决方案1】:

创建一个新的一元“运算符”,我们称之为thereExists,它:

  1. 是否有状态,并且
  2. 声明其子子表达式必须由单个输入记录满足。

具体来说,对于表达式树中thereExists 运算符的每个实例,您应该存储一个位,指示该树节点下的子表达式是否已被目前看到的任何输入记录满足。这些标志最初将设置为false

要继续有效地处理数据集(即逐个输入记录,而不必将整个数据集加载到内存中),您应该首先预处理查询表达式树以提取thereExists 运算符的所有实例的列表.然后,当您读入每个输入记录时,针对每个运算符的子表达式对其进行测试,这些运算符的satisfied 标志仍设置为false。现在满足的任何子表达式都应将其父节点thereExistssatisfied 标志切换为true——最好将满足记录的副本附加到新满足的thereExists 节点,如果您希望看到的不仅仅是对整个查询的“是”或“否”答案。

您只需要评估thereExists 节点上方的树节点一次,在如上所述处理完所有输入记录之后。请注意,任何引用单个记录属性的内容必须出现在树中thereExists 节点下方的某处。树中thereExists 节点上方的所有内容仅允许测试集合的“全局”属性,或使用逻辑运算符(AND、OR、XOR、NOT 等)组合thereExists 节点的结果。逻辑运算符本身可以出现在树中的任何位置。

使用它,您现在可以评估表达式,如

root = "&"
    left = thereExists
        child = "teacher.name=John"
    right = "|"
        left = thereExists
            child = "&"
                left = "student.age>6"
                right = "student.grade=A"
        right = thereExists
            child = "student.name = Billy"

如果记录集合同时包含名为“John”的教师和名为“Billy”的学生或 6 岁以上的 A 学生,则报告“是”,否则为“否”。如果您按照我的建议跟踪令人满意的记录,您还可以在回答“是”的情况下将这些记录丢弃。

您还可以添加第二个运算符类型forAll,它检查其子表达式对于每个 输入记录是否为真。但这可能没那么有用,无论如何你都可以用not(thereExists(not(expr)))模拟forAll(expr)

【讨论】:

  • 难道你不能只消除thereExists 节点并将satisfied 标志存储在逻辑运算符节点中(除了我当前的m_IsMatch 实现之外)?在任何情况下,这都需要按对象类型组织树。目前,解析器是幼稚的,并按照解析表达式的顺序构建树。如果可能的话,我不知道如何对树进行规范化。
  • @Luke:你不能完全消除thereExists,因为如果你这样做了,你怎么能判断一个子表达式对于单个记录是否应该为真,或者是否允许多条记录才能成真?例如。在我的示例中,| 正在组合跨越多个输入记录的子表达式,而嵌套最多的 &amp; 正在组合必须应用于同一记录的子表达式。
  • @Luke:为什么你不能消除它的一个例子是“是否存在一个 A 级且年龄 > 6 的学生?”是与“是否存在成绩为 A 的学生和年龄 > 6 的学生”不同的查询?是不同的查询。如果没有thereExists,您将如何对这两个查询进行不同的编码?
  • 我仍在思考我目前处理它们的方式。您对上述方法是正确的。但问题仍然存在:您如何确保正确排序树?例如,(teacher.name=John AND (student.age&gt;6 AND student.grade=A))((teacher.name=John AND student.age&gt;6) AND student.grade=A) 将生成不同的树,但具有逻辑等价性。您的方法不适用于第二种情况,因为学生元素与教师表达式不匹配。
  • @Luke:您在上一条评论中给出的 2 个查询是逻辑上等价的,因为逻辑运算符 AND 是关联的:x AND (y AND z) = (x AND y) AND z 总是,对于 x 的所有可能值, y 和 z。任何正确实施的查询系统必须为它们提供相同的答案。
猜你喜欢
  • 2017-01-16
  • 2014-06-12
  • 2012-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多