Jesse Lawson

buy me a coffee ☕ / home / blog / tutorials / portfolio / contact

Mar 4, 2020 - Tutorials Python

Python Lecture Notes: Lists, Loops, and Iterators, part II

This is the second half of a two-module session on Lists, Loops, and Iterators. In this module, we’ll be briefly covering the while loop and then continue building confidence and intuition with lists, loops, and iterators.

Ad-hoc Lists

Back in the first part of this two-part series on lists, loops, and iterators, we introduced the for-loop as a way of looping over the contents of a list:


what_we_learned = ["List order is persistent", "Lists are fun"]

for whatever in what_we_learned:
    print(whatever)

We don’t have to have an already constructed list to benefit from the for-loop and list relationship, though. For example, let’s say you wanted to quickly check if Jesse is allowed at the party:


requestor = "Jesse"

for allowed_person in ["Jesse", "Sami", "Alysia", "James", "Remy"]:
    if requestor == allowed_person:
        let_em_through()

I call these “ad-hoc” lists because they’re, well, ad-hoc.

Here’s another example:

def get_language_learning_curve(lang):
    if lang in ["Assembly", "Brainf*ck"]:
        return "Huge"
    elif lang in ["C", "C++", "Rust"]:
        return "Moderate"
    elif lang in ["C#", "Java", "Python", "Ruby"]:
        return "Medium to Moderate"
    else:
        return "Low to Medium"

View an interactive example.

These kinds of lists are helpful when the data is static; the last thing you want to have to do is hunting around a script looking for list data that is constantly being updated!

Splitting sentences into lists

One cool thing about Python is that every string object comes with a special method (which is like a function but attached to the object) called split.

💭 What is a method?

Remember how everything in Python is an object? Well, methods are functions that are attached to objects. When I create a string object, I’m not just creating a sequence of characters in memory–I’m creating a string object that contains a bunch of tools pre-packaged for me. These tools come in the form of special functions called methods that are called just like regular functions– except you add the object name and a dot before it.

For example, a string object like this:

name = "Jesse"

has a special method called upper(). Can you guess what it does?

name = "Jesse"
print(name.upper())

The above would print JESSE. Neat! There are tons of methods to learn about as you dive further and further into Python. Later in this course, we’ll even be making our own!

The split() method will split up a string into a list, where each element is equal to each word in the string. But how does Python know what a word is? Well, let’s take a look!

command = "print hello"

def parse_command():
    words = command.split()
    if words[0] == "print":
        print(words[1])

parse_command()

Check out this interactive example.

In the above code, words is a new list that is created from the return value of command.split(). The .split() after command means *execute the method split() on the command object, which will return a list.

Python assumes that you want it to look for spaces between words, but you can customize the word separator based on your data. For example, if we had the following string:

log_entry = "lawsonjesse^login_failed^20200305"

We can’t just use the default split() method on log_entry because there are no spaces. So what we would do instead is to pass the separator we care about (which is the caret symbol ^ that is obviously breaking up the words) as the sep parameter in split(), like this:

log_entry = "lawsonjesse^login_failed^20200305"

words = log_entry.split(sep='^')

Now, words[0] is lawsonjesse, words[1] is login_failed, and words[2] is 20200305. Neat!

Check out this interactive example.

Parsing sentences and while loops

For the Module 7 assignment, you will be implementing the pay command for a chat bot. This means that you will be expected to have a working chat bot that will wait for input, validate input, then do something based on that input.

To do that, you’ll need to be able to parse a sentence. Parsing means breaking down something into manageable parts, often for the purposes of executing computer instructions.

Let’s pretend that we wanted to have a tiny chat bot named “Butter Robot” that takes two commands:

  • pass the butter. When that command is entered, it will send *begrudgingly passes the butter to you* to the console window.

  • pass the bread. When that command is entered, it will send *begrudgingly passes the bread to you* to the console window.

For any other input, it sends I don't understand. What is my purpose? to the console window. (It’s a very simple chat bot).

# VERSION 1, BUTTER ROBOT
command = input("Your command: ")

words = command.split()

def say_default():
    print("I don't understand. What is my purpose?")

if words[0] == "pass":
    if words[1] == "the":
        if words[2] in ['salt', 'butter']:
            print(f"*Begrudgingly passes the {words[2]} to you*")
        else:
            say_default()
    else:
        say_default()
else:
    say_default()

Look at this interactive example.

We can actually tighten this up a little more, since we know that the pass command requires at least three words: pass, the, and the thing you want to have Butter Robot to pass.

To help validate the input, if the first word is pass, we can just check if the total number of words passed to the chat bot is 3, since that’s how many the pass command expects:

# VERSION 2, BUTTER ROBOT
command = input("Your command: ")

words = command.split()

def say_default():
    print("I don't understand. What is my purpose?")

if words[0] == "pass":
    if len(words) == 3:
        if words[1] == "the":
            if words[2] in ['salt', 'butter']:
                print(f"*Begrudgingly passes the {words[2]} to you*")
            else:
                say_default()
        else:
            say_default()
    else:
        print("USAGE: pass the <something>")
else:
    say_default()

Check out this second interactive example to see how it’s different.

💭 Why not just check if command == "pass the salt"?

Of course, you could just check if the value of command is equal to the entire command string (like if command == "pass the butter"), but this inhibits your ability to write a custom chat parser completely. What if you wanted to have commands like:

  • list ingredients
  • add pepper to potatoes
  • keep summer safe

Now we are talking about issuing commands with variable components to them. For example, the command add pepper to potatoes can be broken down as follows:

  • Word 1: add. This means we should expect the next word to be an ingredient.
  • Word 2: pepper. This means we should expect the next word to be to.
  • Word 3: to. This means we expect the fourth and final word to be a destination.
  • Word 4: potatoes. If this is a valid destination, the command is complete!

We want a chat bot that is customizable and able to “think” a bit, not something that only accepts a static string (like the Module 3 assignment). If we tried to create a chat bot that could parse the above example add command, the number of strings we would need to check for would be equal to the number of different ingredients multiplied by the number of different destinations. So for five ingredents and five destinations, we would have to create 25 if-elif conditions!

As you can see, you have to run the script every time you want to issue a new command–just like in the Module 3 assignment. But we don’t want our chat bot to die as soon as we finish sending ONE command, so how can we get it to keep waiting for input while we are expecting it to run?

The answer is in while loops. Well, just one, to be precise. A while-loop is a block of code that will process over and over and over again while a specific condition is met.

An easy way to do this is to use a boolean variable that will be equal to True as long as we want the while-loop to continue running. Then, we can have some command (say, quit) that sets the boolean variable to False. By doing this, the next time the while-loop cycles around, its condition will now be False and it will stop processing the loop.

Let’s see how that works with our Butter Robot:

# VERSION 3, BUTTER ROBOT

is_running = True

while is_running:
    command = input("Your command: ")

    words = command.split()

    def say_default():
        print("I don't understand. What is my purpose?")

    if words[0] == "pass":
        if len(words) == 3:
            if words[1] == "the":
                if words[2] in ['salt', 'butter']:
                    print(f"*Begrudgingly passes the {words[2]} to you*")
                else:
                    say_default()
            else:
                say_default()
        else:
            print("USAGE: pass the <something>")
    elif words[0] == "quit":
        is_running = False
    else:
        say_default()

print("The butter robot has quit.")

Try the updated interactive example.

There are a few things I want to draw your attention to:

  • if len(words) == 3. This is using the len() function to check the number of words in the variable words, which are really just elements of the list. We can describe these elements with the word “words” becuase we used the split() method to create it (so we’re confident the data is, in fact, words). The purpose of doing this is to immediately validate the number of required words for the pass command. We know that pass requires three parts: the word pass, the word the, and some thing to pass, like salt or butter. By checking, after noticing that the first word is pass, if there are three total words in the command, we can instantly validate whether we should continue processing–or just bail out and let the user know what the chat bot expects to have after the pass word is the first in a sentence.

  • elif words[0] == "quit": is_running = False. This little piece right here is what’s responsible for bailing out of the while-loop. At the top of our script we have a boolean variable is_running and we instantiate it with a value of True. Then, our while loop starts processing–and continues to process, over and over again, as long as is_running continues to evaluate to True. In our big if-elif-else block that parses what the user inputs for a command, we check here if the first word (words[0]) is “quit”–and if it is, we set the boolean variable to False. This causes the while-loop to discontinue processing, which effectively ends our program.

Much of the validation so far has been about strings. If you have an integer variable that you’re trying to validate against, you only need to remember to cast the variable type to int–which you do with the function int(). You can also check if some variable is numeric by using the isnumeric() method.

Please review and study this last interactive example before you begin on your Module 7 assignment.

Happy coding!