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.

\$\endgroup\$
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.

\$\endgroup\$
  • \$\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 Nov 4 '17 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 Nov 4 '17 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 Nov 4 '17 at 3:45
0
\$\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

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.