Sergey Bushmanov
Sergey Bushmanov

Reputation: 25219

Constructing fast boolean logic out of a string

Let's say I have a rather contrived example like:

X = {'a':True, 'b':False}
x = ("a","and","b")
y = ("a","or","b")

def func(z):
    return X[z[0]] 'and/or' X[z[2]]

Is there a fast way to construct such a logic:

  1. without eval, because it's a significant overhead for a function evaluated in a loop ~1e6 times
  2. without if/else logic because it again will add overhead and real logic could be lengthy?

Upvotes: 1

Views: 87

Answers (2)

Ajax1234
Ajax1234

Reputation: 71461

You can use operator with recursion to handle arbitrary expression strings:

import operator
X = {'a':True, 'b':False}
def _eval(s):
   l, o = None, None
   while (n:=next(s, None)) not in {')', None}:
      if n in {'and', 'or'}:
         o = n
      else:
         v = n if n != '(' else _eval(s)
         if l is None:
            l = v
         else:
            s = iter([getattr(operator, f'{o}_')(X.get(l, l), X.get(v, v)), *s])
            l, o = None, None
   return l

import re
def run_eval(s):
   return _eval(iter(re.findall('\(|\)|\w+', s)))

print(run_eval('a and b'))
print(run_eval('a or b'))
print(run_eval('a and (b or (a and b)) or b'))

Output:

False
True
False

Edit: evaluating tuple-based input:

from collections import deque
X = {'a':True, 'b':False, 'c':True}
def _eval(t):
   d = deque(t) #using deque for faster element removal and addition
   while len(d) > 1:
      a, b, c = [d.popleft() for _ in range(3)]
      v1, v2 = X[a] if a in X else (a if not isinstance(a, tuple) else _eval(a)), X[c] if c in X else (c if not isinstance(c, tuple) else _eval(c))
      d.appendleft(getattr(operator, f'{b}_')(v1, v2))
   return d[0]

def run_eval(t):
   return _eval(t)

print(run_eval(("a", "and","b")))
print(run_eval(("a", "or","b", "or", "c")))
print(run_eval((("a","and","b"), "or",("a","and","b"))))

Output:

False
True
False

Upvotes: 3

lllrnr101
lllrnr101

Reputation: 2343

You can make use of the built-in operator library. Something like below:

>>> X
{'a': True, 'b': False}
>>> mapping
{'and': <built-in function and_>}
>>> 
>>> def f(z):
...     return mapping[z[1]](X[z[0]],X[z[2]])
... 
>>> f('a and b'.split())
False
>>> f('a and a'.split())
True
>>> 

Upvotes: 1

Related Questions