Pregunta

Tengo algunas expresiones lógicas booleanas generadas dinámicamente, como:

  • (A o B) y (C o D)
  • A o (A y B)
  • A
  • vacío - se evalúa como TRUE

Los marcadores de posición son reemplazados con booleanos. En caso de que,

  1. Convertir esta información a una expresión de Python como True or (True or False) y eval él?
  2. Crear un árbol binario en el que un nodo es o bien un bool o Conjunction objeto / Disjunction y evaluarlo de forma recursiva?
  3. convertirlo en S-expresiones anidadas y utilizar un analizador de Lisp?
  4. Algo más?

Se aceptan sugerencias.

¿Fue útil?

Solución

No debería ser difícil en absoluto escribir un evaluador que puede manejar esto, por ejemplo usando pyparsing . Sólo tiene unas pocas operaciones para manejar (y, o, y la agrupación?), Por lo que debe ser capaz de analizar y evaluar por sí mismo.

No debería ser necesario para formar explícitamente el árbol binario para evaluar la expresión.

Otros consejos

Aquí hay un pequeño módulo (posiblemente, 74 líneas, incluyendo espacios en blanco) he construido en aproximadamente una hora y media (más casi una hora para refactorización):

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

Las pruebas simples dan:

>>> 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

[parcialmente fuera de tema posiblemente]

Tenga en cuenta, la puede configurar fácilmente las fichas (ambos operandos y operadores) se utiliza con los pobres-Mans medios previstos dependencia de inyección (token_to_char=token_to_char y amigos) para tener múltiples evaluadores diferentes al mismo tiempo (solo de restablecer el "inyectada -by-default" globales le dejará con un solo comportamiento).

Por ejemplo:

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)

da:

>>> 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

Si configura predice con los locales y globales que le interesan, entonces debería ser capaz de pasar con seguridad a ellos, junto con la expresión en eval() .

Suena como un pedazo de la torta usando el módulo de lógica sympy. Incluso tienen un ejemplo de ello en los documentos: http: //docs.sympy. org / 0.7.1 / módulos / logic.html

Estoy escribiendo esto porque tenía un resolver un problema similar en la actualidad y que estaba aquí cuando yo estaba buscando en busca de pistas. (Parser Boolean con tokens de cadena arbitrarias que se convierten a valores booleanos más adelante).

Después de considerar diferentes opciones (la implementación de una solución de mí mismo o usar algún paquete), que se establecieron en el uso de Lark, https://github.com/lark-parser/lark

Es fácil de usar y muy rápido si se utiliza LALR (1)

A continuación se muestra un ejemplo que podría coincidir con la sintaxis

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())

Nota:. La expresión regular que elegí no coincide con la cadena vacía a propósito (Lark por alguna razón no lo permite)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top