在 Python 中动态评估简单的布尔逻辑
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2467590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Dynamically evaluating simple boolean logic in Python
提问by a paid nerd
I've got some dynamically-generated boolean logic expressions, like:
我有一些动态生成的布尔逻辑表达式,例如:
- (A or B) and (C or D)
- A or (A and B)
- A
- empty - evaluates to True
- (A 或 B) 和 (C 或 D)
- A 或 (A 和 B)
- 一个
- 空 - 评估为 True
The placeholders get replaced with booleans. Should I,
占位符被替换为布尔值。我是不是该,
- Convert this information to a Python expression like
True or (True or False)
andeval
it? - Create a binary tree where a node is either a
bool
orConjunction
/Disjunction
object and recursively evaluate it? - Convert it into nested S-expressions and use a Lisp parser?
- Something else?
- 将此信息转换为 Python 表达式,例如
True or (True or False)
和eval
它? - 创建一个二叉树,其中一个节点是一个
bool
或Conjunction
/Disjunction
对象并递归评估它? - 将其转换为嵌套的 S 表达式并使用 Lisp 解析器?
- 还有什么?
Suggestions welcome.
欢迎提出建议。
采纳答案by Mike Graham
It shouldn't be difficult at all to write a evaluator that can handle this, for example using pyparsing. You only have a few operations to handle (and, or, and grouping?), so you should be able to parse and evaluate it yourself.
编写一个可以处理这个问题的评估器应该不难,例如使用pyparsing。您只需要处理几个操作(和、或和分组?),因此您应该能够自己解析和评估它。
You shouldn't need to explicitly form the binary tree to evaluate the expression.
您不需要显式地形成二叉树来评估表达式。
回答by mlvljr
Here's a small (possibly, 74 lines including whitespace) module I built in about an hour and a half (plus almost an hour to refactoring):
这是我用大约一个半小时(加上将近一个小时的重构时间)构建的一个小(可能有 74 行,包括空格)模块:
str_to_token = {'True':True,
'False':False,
'and':lambda left, right: left and right,
'or':lambda left, right: left or right,
'(':'(',
')':')'}
empty_res = True
def create_token_lst(s, str_to_token=str_to_token):
"""create token list:
'True or False' -> [True, lambda..., False]"""
s = s.replace('(', ' ( ')
s = s.replace(')', ' ) ')
return [str_to_token[it] for it in s.split()]
def find(lst, what, start=0):
return [i for i,it in enumerate(lst) if it == what and i >= start]
def parens(token_lst):
"""returns:
(bool)parens_exist, left_paren_pos, right_paren_pos
"""
left_lst = find(token_lst, '(')
if not left_lst:
return False, -1, -1
left = left_lst[-1]
#can not occur earlier, hence there are args and op.
right = find(token_lst, ')', left + 4)[0]
return True, left, right
def bool_eval(token_lst):
"""token_lst has length 3 and format: [left_arg, operator, right_arg]
operator(left_arg, right_arg) is returned"""
return token_lst[1](token_lst[0], token_lst[2])
def formatted_bool_eval(token_lst, empty_res=empty_res):
"""eval a formatted (i.e. of the form 'ToFa(ToF)') string"""
if not token_lst:
return empty_res
if len(token_lst) == 1:
return token_lst[0]
has_parens, l_paren, r_paren = parens(token_lst)
if not has_parens:
return bool_eval(token_lst)
token_lst[l_paren:r_paren + 1] = [bool_eval(token_lst[l_paren+1:r_paren])]
return formatted_bool_eval(token_lst, bool_eval)
def nested_bool_eval(s):
"""The actual 'eval' routine,
if 's' is empty, 'True' is returned,
otherwise 's' is evaluated according to parentheses nesting.
The format assumed:
[1] 'LEFT OPERATOR RIGHT',
where LEFT and RIGHT are either:
True or False or '(' [1] ')' (subexpression in parentheses)
"""
return formatted_bool_eval(create_token_lst(s))
The simple tests give:
简单的测试给出:
>>> print nested_bool_eval('')
True
>>> print nested_bool_eval('False')
False
>>> print nested_bool_eval('True or False')
True
>>> print nested_bool_eval('True and False')
False
>>> print nested_bool_eval('(True or False) and (True or False)')
True
>>> print nested_bool_eval('(True or False) and (True and False)')
False
>>> print nested_bool_eval('(True or False) or (True and False)')
True
>>> print nested_bool_eval('(True and False) or (True and False)')
False
>>> print nested_bool_eval('(True and False) or (True and (True or False))')
True
[Partially off-topic possibly]
[可能部分离题]
Note, the you can easily configure the tokens (both operands and operators) you use with the poor-mans dependency-injection means provided (token_to_char=token_to_char
and friends) to have multiple different evaluators at the same time (just resetting the "injected-by-default" globals will leave you with a single behavior).
请注意,您可以轻松地配置与提供的穷人依赖注入方法(token_to_char=token_to_char
和朋友)一起使用的令牌(操作数和运算符)以同时拥有多个不同的评估器(只需重置“默认注入" 全局变量会给你一个单一的行为)。
For example:
例如:
def fuzzy_bool_eval(s):
"""as normal, but:
- an argument 'Maybe' may be :)) present
- algebra is:
[one of 'True', 'False', 'Maybe'] [one of 'or', 'and'] 'Maybe' -> 'Maybe'
"""
Maybe = 'Maybe' # just an object with nice __str__
def or_op(left, right):
return (Maybe if Maybe in [left, right] else (left or right))
def and_op(left, right):
args = [left, right]
if Maybe in args:
if True in args:
return Maybe # Maybe and True -> Maybe
else:
return False # Maybe and False -> False
return left and right
str_to_token = {'True':True,
'False':False,
'Maybe':Maybe,
'and':and_op,
'or':or_op,
'(':'(',
')':')'}
token_lst = create_token_lst(s, str_to_token=str_to_token)
return formatted_bool_eval(token_lst)
gives:
给出:
>>> print fuzzy_bool_eval('')
True
>>> print fuzzy_bool_eval('Maybe')
Maybe
>>> print fuzzy_bool_eval('True or False')
True
>>> print fuzzy_bool_eval('True or Maybe')
Maybe
>>> print fuzzy_bool_eval('False or (False and Maybe)')
False
回答by Ignacio Vazquez-Abrams
回答by Shnatsel
Sounds like a piece of cake using SymPy logic module. They even have an example of that on the docs: http://docs.sympy.org/0.7.1/modules/logic.html
使用 SymPy 逻辑模块听起来像小菜一碟。他们甚至在文档中有一个例子:http: //docs.sympy.org/0.7.1/modules/logic.html
回答by aripy887
I am writing this because I had a solve a similar problem today and I was here when I was looking for clues. (Boolean parser with arbitrary string tokens that get converted to boolean values later).
我写这篇文章是因为我今天解决了一个类似的问题,而我在寻找线索的时候就在这里。(具有任意字符串标记的布尔解析器,稍后将转换为布尔值)。
After considering different options (implementing a solution myself or use some package), I settled on using Lark, https://github.com/lark-parser/lark
在考虑了不同的选择(自己实现解决方案或使用一些包)后,我决定使用 Lark,https://github.com/lark-parser/lark
It's easy to use and pretty fast if you use LALR(1)
如果您使用 LALR(1),则它易于使用且速度非常快
Here is an example that could match your syntax
这是一个可以匹配您的语法的示例
from lark import Lark, Tree, Transformer
base_parser = Lark("""
expr: and_expr
| or_expr
and_expr: token
| "(" expr ")"
| and_expr " " and " " and_expr
or_expr: token
| "(" expr ")"
| or_expr " " or " " or_expr
token: LETTER
and: "and"
or: "or"
LETTER: /[A-Z]+/
""", start="expr")
class Cleaner(Transformer):
def expr(self, children):
num_children = len(children)
if num_children == 1:
return children[0]
else:
raise RuntimeError()
def and_expr(self, children):
num_children = len(children)
if num_children == 1:
return children[0]
elif num_children == 3:
first, middle, last = children
return Tree(data="and_expr", children=[first, last])
else:
raise RuntimeError()
def or_expr(self, children):
num_children = len(children)
if num_children == 1:
return children[0]
elif num_children == 3:
first, middle, last = children
return Tree(data="or_expr", children=[first, last])
else:
raise RuntimeError()
def get_syntax_tree(expression):
return Cleaner().transform(base_parser.parse(expression))
print(get_syntax_tree("A and (B or C)").pretty())
Note: the regex I chose doesn't match the empty string on purpose (Lark for some reason doesn't allow it).
注意:我选择的正则表达式故意与空字符串不匹配(Lark 出于某种原因不允许这样做)。