解析逻辑表达式

3
我有一个任务,需要根据用户指定的逻辑表达式筛选Pandas DataFrame。现在,我看到了一个名为PyParser或LARK的模块,我想使用它们,但我无法弄清楚如何设置它们。
我有几个运算符,如CONTAINSEQUALFUZZY_MATCH等。此外,我想将一些表达式组合成更复杂的表达式。
示例表达式: ColumnA CONTAINS [1, 2, 3] AND (ColumnB FUZZY_MATCH 'bla' OR ColumnC EQUAL 45) 因此,我希望有一些结构化的字典或列表,按照执行顺序列出操作级别。因此,这个示例表达式的期望结果应该是:
[['ColumnA', 'CONTAINS', '[1, 2, 3]'], 'AND', [['ColumnB', 'FUZZY_MATCH', 'bla'], OR, ['ColumnC', 'EQUAL', '45']]]

或以字典形式:

{
  'EXPR1': {
    'col': 'ColumnA', 
    'oper': 'CONTAINS', 
    'value': '[1, 2, 3]']
  },
  'OPERATOR': 'AND', 
  'EXPR2': {
    'EXPR21': {
      'col': 'ColumnB', 
      'oper': 'FUZZY_MATCH', 
      'value': 'bla'
    }, 
    'OPERATOR': OR, 
    'EXPR22': {
      'col': 'ColumnC', 
      'oper': 'EQUAL', 
      'value': '45'
    }
  }
}

或者类似这样。如果你有更好的结果结构方式,请提出建议。我还是相对新手,所以我确信这可以改进。


我看到你已经得到了一个答案,但如果你有兴趣使用Lark解决方案,请告诉我。 - Erez
1个回答

1

有趣的问题 :)

看起来是一个相对简单的应用逆波兰表达式算法。
我已经编写了代码,可以解析类似于"((20 - 10 ) * (30 - 20) / 10 + 10 ) * 2"的表达式,可以在这里查看。

import re


def tokenize(str):
   return re.findall("[+/*()-]|\d+", expression)

def is_number(str):
    try:
        int(str)
        return True
    except ValueError:
        return False


def peek(stack):
    return stack[-1] if stack else None


def apply_operator(operators, values):
    operator = operators.pop()
    right = values.pop()
    left = values.pop()
    values.append(eval("{0}{1}{2}".format(left, operator, right)))


def greater_precedence(op1, op2):
    precedences = {"+": 0, "-": 0, "*": 1, "/": 1}
    return precedences[op1] > precedences[op2]


def evaluate(expression):
    tokens = tokenize(expression)
    values = []
    operators = []
    for token in tokens:
        if is_number(token):
            values.append(int(token))
        elif token == "(":
            operators.append(token)
        elif token == ")":
            top = peek(operators)
            while top is not None and top != "(":
                apply_operator(operators, values)
                top = peek(operators)
            operators.pop()  # Discard the '('
        else:
            # Operator
            top = peek(operators)
            while top is not None and top != "(" and greater_precedence(top, token):
                apply_operator(operators, values)
                top = peek(operators)
            operators.append(token)
    while peek(operators) is not None:
        apply_operator(operators, values)

    return values[0]


def main():
    expression = "((20 - 10 ) * (30 - 20) / 10 + 10 ) * 2"
    print(evaluate(expression))


if __name__ == "__main__":
    main()

我认为我们可以稍微修改代码使其适用于你的情况:
  1. 我们需要修改tokenize()中的字符串分割方式。
    基本上,给定字符串ColumnA CONTAINS [1, 2, 3] AND (ColumnB FUZZY_MATCH 'bla' OR ColumnC EQUAL 45),我们希望得到一个标记列表:
    ['ColumnA', 'CONTAINS', '[1, 2, 3]', 'AND', '(', 'ColumnB', 'FUZZY_MATCH', "'bla'", 'OR', 'ColumnC', 'EQUAL', '45', ')']
    这将高度依赖于输入字符串的复杂程度,并且需要一些字符串处理,但它相当简单,我会留给您来完成。
  2. 修改is_number()函数以检测像ColumnA[1, 2, 3]等内容。
    基本上,除了谓词CONTAINS/FUZZY_MATCH/EQUAL、运算符AND/OR和括号(/)之外的所有内容。
  3. 修改greater_precedence(op1, op2),如果op1['CONTAINS', 'EQUAL', ..]中,op2['AND', 'OR'],则返回true。
    这是因为我们希望在AND/OR之前始终评估containsequals
  4. 修改apply_operator(operators, values)以实现如何计算布尔表达式ColumnA CONTAINS [1, 2, 3]或表达式true AND false的逻辑。
    请记住,这里的CONTAINS/FUZZY_MATCH/EQUAL/AND/OR等都是运算符。
    可能需要编写许多if-else情况,因为可能有很多不同的运算符。

好的,感谢您详尽和完整的回答 :) 看起来这正是我所需要的。我会在接下来的几天进行测试。 - Novak
当然。如果它起作用了,请不要忘记接受答案并关闭问题 ;) - Anmol Singh Jaggi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接