I have a dictionary of the form:
dictionary = {'a':25, 'ab':3.3, '(cd)': 4, 'ef':35, 'gh':12.2, etc.}
a user enters a comma-separated phrase (search string) like:
'a > 5, 0 < (cd) < 6, ef < 35'
Above we have three conditions, but it could be any number of conditions: What I would like is to check if my dictionary satisfies ALL search conditions.
if dict meets all conditions:
do somthing
else:
do something else
I can take the search string, do split(',')
, loop over each condition, extract the key, the comparison operator (<
or >
), and the number, and check if my dictionary meets all conditions. In the example above dictionary['a'] > 5
is met, 0 < dictionary['(cd)'] < 6
is also met, but dictionary['ef'] < 35
is not met.
Is there a quicker and more elegant way to do this? A better and smarter way to go from 'a > 5'
to dictionary['a'] > 5?
For example, can I convert my dictionary to a Pandas data frame, and do some checks? Is there an evaluation method, or something similar in Python aside from the brute force method that I am trying?
It isn't very hard to build a parser for this. You can use the operator
package to handle the conditions, and other than that, it's just a bunch of formatting and looping data.
import operator, re
# dictionary of operators
operators = {'<=':operator.le,
'>=':operator.ge,
'>' :operator.gt,
'<' :operator.lt,
'==':operator.eq,
'!=':operator.ne
}
# format operators keys for regex group
_oper = '|'.join(map(re.escape, operators.keys()))
# regex
oper = re.compile(fr'\s*({_oper})\s*').split
number = re.compile(r'-?\d*(\.\d+)?').fullmatch
string = re.compile(r'("|\')(?P<str>.*)\1').fullmatch
def cast(value:str) -> float|int|bool|str|None:
out = None
if (v := value.lower()) in ('true', 'false'):
out = v == "true"
elif m := string(value):
out = m.group('str')
elif m := number(value):
out = (int,float)['.' in v](v)
return out
def check_conditions(data:dict, conditions:str) -> bool:
# stores condition results
facts = []
for cond in conditions.split(','):
# stores values and operators
v, o = [], []
# parse values and operators
for c in oper(cond):
if (c := c.strip()) in operators:
# store operator function
o.append(operators.get(c))
else:
# try data[c] else cast c
v.append(data.get(c, cast(c)))
# make sure values length is one more than operators
if (len(v) - len(o)) != 1:
raise ValueError
# call all operator functions on values and store results
facts += [o[i](*v[i:i+2]) for i in range(len(o))]
return all(facts)
data = {'a':25, 'ab':3.3, '(cd)': 4, 'ef':35, 'gh':.2, 'ij':"hello", 'kl':False, "mn":None}
conds = 'mn==None, a>5, 0<(cd)<5, 35==ef!=34>12<20, ij==\'hello\''
print(check_conditions(data, conds)) #True