I wrote this code as a part of a series of major upgrades to my nestable Robot Framework For Loop that will allow it to evaluate logical expressions written in a single cell from the Robot Framework side, which are parsed in my code as strings.
The user of this method can pass it any of the following expressions on the Robot Framework side (as a small sample of what it supports), and it will work:
${ROBOT_FRAMEWORK_VARIABLE_1}==${ROBOT_FRAMEWORK_VARIABLE_2}
pythonic_variable_1<pythonic_variable_2
(not implemented in the public version yet)1<2
(small arguments with ints)spaced variable 1 >= spaced variable 2
${mixed_variable_1} =>mixed variable 2
It further supports !=
, <=
, and =<
, but it doesn't include =
, !<
, !>
, or any three-character expressions (e.g. !<=
). This was a design choice on my part.
@staticmethod
def _evaluate_boolean_string(condition):
condition = str(condition).replace(" ", "") # Cast the condition as a string with no whitespaces
inverse = False # Assume no !
less = False # Assume no <
greater = False # Assume no >
equal = False # Assume no =
second_equal = False # Assume no ==
# Count the number of conditions that are true.
count = 0 # Initialize the count as 0
start = len(condition) # Initialize the starting index as the last index in condition
start_temp = start
# For all of the parameters...
for param in ['!', '<', '>', '=']:
# Based on which parameter I'm looking at, find the starting index.
if param in condition:
# Advance the count of parameters
count = count + 1
# If the count goes higher than 2, exit the loop early.
if count > 2:
break
# Otherwise, set the parameter to True
elif param == '!':
inverse = True
elif param == '<':
less = True
elif param == '>':
greater = True
elif param == '=' and '==' in condition:
equal = True
second_equal = True
elif param == '=':
equal = True
start_temp = condition.find(param)
# If there is a first variable and...
# If the temporary starting index is less than the current starting index...
if start_temp < start and start_temp != (0 or len(condition)):
# Set the starting index of the comparator to the temporary starting index
start = start_temp
# If there is no first variable or second variable or the user goofed then return False
elif start_temp == 0 \
or (param == '=' and start_temp == len(condition)) \
or count > 2 \
or (param == '=' and count == 0):
# Return False
return False
# Set the first variable to the first variable entered.
first = condition[:start - 1]
# Set the second variable to the second variable entered.
second = condition[start + count:]
# If an exact set of conditions is met, return True. Else, return False.
if (greater or less) and not (greater and less):
if equal:
if greater:
if first >= second:
return True
else:
return False
elif less:
if first <= second:
return True
else:
return False
else:
return False
else:
if greater:
if first > second:
return True
else:
return False
elif less:
if first < second:
return True
else:
return False
else:
return False
elif second_equal or (inverse and equal):
if second_equal:
if first == second:
return True
else:
return False
elif inverse and equal:
if first != second:
return True
else:
return False
else:
return False
By necessity this method needs to be as fast as possible since I'm planning on using it a lot, so I'm primarily after performance-based and simplification-based suggestions.
2 Answers 2
in terms of small optimizations, this block
elif second_equal or (inverse and equal):
if second_equal:
if first == second:
return True
else:
return False
elif inverse and equal:
if first != second:
return True
else:
return False
can be replaced by the simpler and faster
elif second_equal or (inverse and equal):
return first == second:
elif inverse and equal:
return first != second:
similarly, whenever you have
if condition:
return true
else:
return false
you should replace it with return condition
You should also break up your code into smaller functions for readability.
-
\$\begingroup\$ Thank you. I'm actually a Python beginner and was never formally taught, so this helps a lot. Should I look into using a dictionary to shorten the first For loop? \$\endgroup\$Brandon Olson– Brandon Olson2017年11月04日 03:28:56 +00:00Commented Nov 4, 2017 at 3:28
-
\$\begingroup\$ Yes that would be a good idea. (Note that does not mean it will be better for this specific problem, but looking at things like that will help your code in general). \$\endgroup\$Oscar Smith– Oscar Smith2017年11月04日 03:43:39 +00:00Commented Nov 4, 2017 at 3:43
-
\$\begingroup\$ You should go through this code and post a rewritten version of it as an answer. This code has a lot of work that could be done to make it better. \$\endgroup\$Oscar Smith– Oscar Smith2017年11月04日 03:45:34 +00:00Commented Nov 4, 2017 at 3:45
Re-written as requested.
@staticmethod
def _evaluate_boolean_string(condition):
condition = str(condition).replace(" ", "") # Cast the condition as a string with no whitespaces
inverse = False # Assume no !
less = False # Assume no <
greater = False # Assume no >
equal = False # Assume no =
second_equal = False # Assume no ==
# Count the number of conditions that are true.
count = 0 # Initialize the count as 0
start = len(condition) # Initialize the starting index as the last index in condition
start_temp = start
# For all of the parameters...
for param in ['!', '<', '>', '=']:
# Based on which parameter I'm looking at, find the starting index.
if param in condition:
# Advance the count of parameters
count = count + 1
# If the count goes higher than 2, exit the loop early.
if count > 2:
break
# Otherwise, set the parameter to True
elif param == '!':
inverse = True
elif param == '<':
less = True
elif param == '>':
greater = True
elif param == '=' and '==' in condition:
equal = True
second_equal = True
elif param == '=':
equal = True
start_temp = condition.find(param)
# If there is a first variable and...
# If the temporary starting index is less than the current starting index...
if start_temp < start and start_temp != (0 or len(condition)):
# Set the starting index of the comparator to the temporary starting index
start = start_temp
# If there is no first variable or second variable or the user goofed then return False
elif start_temp == 0 \
or (param == '=' and start_temp == len(condition)) \
or count > 2 \
or (param == '=' and count == 0):
# Return False
return False
# Set the first variable to the first variable entered.
first = condition[:start - 1]
# Set the second variable to the second variable entered.
second = condition[start + count:]
# If an exact set of conditions is met, return True. Else, return False.
if (greater or less) and not (greater and less):
if equal:
if greater:
return first >= second
elif less:
return first <= second
elif greater:
return first > second
elif less:
return first < second
elif second_equal:
return first == second
elif inverse and equal:
return first != second
else:
return False
I don't currently have the code available to me, so I don't feel comfortable investigating the dictionary or sub-functions, but that's the result of the small optimizations suggested. It already looks much cleaner and shorter, at least on the bottom half.
EDIT: After more editing, I've transferred everything over to an array-style implementation, further shortened some of the logic, and divided the code into sub-methods. Still interested in any further optimization suggestions anyone might have.
@staticmethod
def _evaluate_boolean_string(condition):
def _eval(arg_1, arg_2):
if (t[0][1] or t[0][2]) and not (t[0][1] and t[0][2]): # If it has either > or < in it, but not both
if t[0][3]: # If it has = in it
if t[0][1]: # If it's >=
return arg_1 >= arg_2
else: # If it's <=
return arg_1 <= arg_2
else:
if t[0][1]: # If it's >
return arg_1 > arg_2
else: # If it's <
return arg_1 < arg_2
elif t[0][4]: # If it's ==
return arg_1 == arg_2
elif t[0][0] and t[0][3]: # If it's !=
return arg_1 != arg_2
else: # In case of Tester
return False
def _find_bool():
begin = len(condition) # Initialize the starting index as the last index in condition
# For all of the parameters...
for index in t[2]:
# Find the location of the start of the boolean parameters
temp = condition.find(str(t[1][int(index)]))
# If the location exists and is less than start...
if temp < begin and temp != -1:
# Set start to the location
begin = temp
# If the input was bad, return -1
if sum(t[0]) > 2 or sum(t[0]) == 0 or begin == len(condition):
return -1
else:
return begin
# Cast the condition as a string with no whitespaces
condition = str(condition).replace(" ", "")
# Initialize the t-table with default values
t = [[False, False, False, False, False],
['!', '<', '>', '=', '=='],
[0, 1, 2, 3, 4]]
# Find the start of the Boolean expression
start = _find_bool()
# Evaluate the expression
return _eval(condition[:start - 1], condition[start + sum(t[0]):])
Explore related questions
See similar questions with these tags.