2
\$\begingroup\$

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.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Nov 3, 2017 at 23:20
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

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.

answered Nov 4, 2017 at 2:56
\$\endgroup\$
3
  • \$\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\$ Commented 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\$ Commented 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\$ Commented Nov 4, 2017 at 3:45
1
\$\begingroup\$

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]):])
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.