Branching in Python (Part 3)

Example: finding the minimum of three numbers

As another example, consider the problem of finding the minimum of three inputs, or more accurately, one of the three which has minimum value, as some may be equal.

Determining the cases:

  • a is smaller than b
    • a is smaller than c
    • a is not smaller than c
  • a is not smaller than b
    • b is smaller than c
    • b is not smaller than c

We can split up the cases by first comparing a and b. If a is smaller than b, then either a or c is the minimum, so we compare them. This results in two subcases. If instead a is not smaller than b, then at least one of b and c has the minimum value. Again, this results in two subcases.


Writing the function

def min_3(a, b, c):
    if a < b:
         if a < c:
             return a
         else:
             return c
    else:
         if b < c:
             return b
         else: 
             return c

We use if and else for the first split and then if and else again for the subcases. There are four different cases, two with the same outcome, which isn't really a problem. What is a problem is that it isn't so easy to read the code and understand what conditions lead to each outcome. Whenever code isn't clear, it is possible that there is an error lurking within. Try to make the conditions for each outcome clear.


Finding a clearer solution

Determining the cases:

  • a is not bigger than b or c
  • b is not bigger than a or c
  • all other cases

Let's try again, this time with clarity in mind. In one case, a is identified as a minimum value as it is not bigger than b or c. Identifying b as a minimum value can be viewed analogously. That leaves all other cases, namely that c is a minimum value.


Writing the function

This code is clearer to understand as we can see the correlation between the conditions and the outcomes:

def min_3(a, b, c):
    if a <= b and a <= c:
         return a
    elif b <= a and b <= c:
         return b
    else:
         return c

Recipe

Summarizing what we've learned, we can form a recipe for using branching:

Branching recipe

  • Identify cases
  • Order cases
  • Choose conditions
  • Simplify

First, we identify the various cases. Then, we figure out a logical order. Choosing conditions isn't completely separate from the order since a good order leads to good conditions. If we discover that something simpler is possible, we simplify our code.


Example: is input a string that is a question

Let's put the recipe to use in a modification of a function we looked at earlier. The function takes any input, returning the string “Question” only if the input is a string and is a question, that is, ends with a question mark. Otherwise, the function returns the string “Not question”.

Identify cases:

We have four cases:

  • String with last character question mark
  • String with last character not question mark
  • Empty string
  • Not a string

In the first case, we have a question. If the input is a string that doesn't end with a question mark, or is an empty string, or is not a string at all, the string ”Not question” is returned.

Order cases:

How might we order the cases to make the conditions easy to write?

  • String with last character question mark (Case 3)
  • String with last character not question mark (Case 4)
  • Empty string (Case 2)
  • Not a string (Case 1)

Since we'll need to use string operations on the strings, we'll make the first condition check for nonstrings. To use an index on a string, we need it to be nonempty, so we'll eliminate empty strings from consideration by checking for them next. We can then check for the question mark in the last position, leaving the last case to cover all nonempty strings that do not end with a question mark.

Choose conditions:

How should we express these conditions? Again, we keep in mind that a condition is considered only if all previous ones are false.

def is_question(any):
    if type(any) != type(""):
        return "Not question"

First, we check for nonstrings.


    if type(any) != type(""):
        return "Not question"

Then, we check for empty strings.


    elif len(any) < 1:
        return "Not question"
    elif any[-1] == "?": 
        return "Question"
    else:
        return "Not question"

Finally, we check the last character, returning the string "Question" in the case in which it is a question mark.


Here is our function when all parts are put together:

def is_question(any):
    if type(any) != type(""):
        return "Not question"
    elif len(any) < 1:
        return "Not question"
    elif any[-1] == "?": 
        return "Question"
    else:
        return "Not question"

Since there are only two outcomes, can we simplify the code?

Simplify:

One option is to combine the first two conditions:

def is_question(any):
    if type(any) != type("") or len(any) < 1:
        return "Not question"
    elif any[-1] == "?": 
        return "Question"
    else:
        return "Not question"

Here we are taking advantage of short-cut evaluation to ensure that the length function is only applied to something for which the type is guaranteed to be a string.