Jesse Lawson

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

Feb 15, 2020 - Tutorials Python

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

Lists

Remember the string slice examples from the Functions notes? We were talking about parsing log files and how frustrating it would be to store the status of each line in a log file (whether they were successful logins or failed logins) in separate variables–one for each line.

What we would need to do instead is store each line as a separate element of the same thing–so that if we were to get each element of that thing, we could do something with the individual line each element represents.

If you have a group of values that you want to store all together in a single variable, you can do that by creating a list. A list is a variable that holds many values. For example, you might store the names of all the photos in a directory in a list, or you could store each line from a log file in a list. One reason to use lists is to have multiple items stored together so that we can perform the same operations on each element.

We use “element” and “item” interchangeably to refer to each item in a list.

A list is declared with brackets ([ and ]), where each item is included inside those brackets:

my_friends = ["Jane", "Nhadira", "Amit"]

The above creates a list of strings named my_friends with three elements: "Jane", "Nhadira", and "Amit".

We can access the individual elements using the same brackets we used to declare the list, but inside we would use the index. An element’s index is its numerical position in the list. Lists are something in Python that are guaranteed to have persistent ordering, which means the first element of a list will always be in the first position unless you explicitly delete it or move it.

Lists in Python are zero-indexed, which means the first element in the list is always at position 0–not position 1.

In my_friends, for example, Nhadira is at index 1 and Amit is at index 2.

1
2
3
4
5
my_friends = ["Jane", "Nhadira", "Amit"]

print(f"{my_friends[0]} is at index 0")
print(f"{my_friends[1]} is at index 1")
print(f"{my_friends[2]} is at index 2")

Here’s an interactive example.

Understanding Lists in Python 3 is a complete tutorial on Lists in Python 3. Work through it before continuing.

Loops

Looping is the process of executing a block of code multiple times according to some kind of condition. One of the most common kinds of looping is called the for-loop. A for-loop is one where you say “for x in y, do a", where a is a set of statements you want to perform on x,
which is a variable you declare to hold the value of the next element in y.

A for-loop automatically runs once for each element in the list y.

We can rewrite our example above with my friends Jane, Nhadira, and Amit by using a for-loop like this:

1
2
3
4
my_friends = ["Jane", "Nhadira", "Amit"]

for friend in my_friends: 
    print(f"{friend} is my friend!")

Here’s an interactive example.

Study the following resources before continuing:

Iterators

As you can see, if we want to do the same thing to each element in a list, a for-loop is perfect. In the above example, friend will be a different element of my_friends for every iteration of the loop.

Speaking of iterations, any time we say we are “looping” we are invoking the concept of iteration. When we iterate “over” a list, we are looping over each item in the list and keeping track of what position we are in with something called an iterator.

Python will generate you an iterator if you enumerate a list using the cleverly named function enumerate. Enumeration means you are placing a number next to each item and incrementing the number each time you place it. So the first item is 0, the second item is 1, the third is 2, and so on and so on.

You enumerate a list by calling the function enumerate() and passing in the list you want to enumerate. You also must declare the for-loop differently. Instead of the “for x in y, do a” syntax, it’s now this:

for <iterator>,<item> in enumerate(<items>):
    do stuff

We must declare two new variables as part of the for-loop statement: an iterator, which keeps track of what position we are at in the list, and a item, which is a single element from the list based on the position we track with iterator.

Let’s take a look:

my_friends = ["Jane", "Nhadira", "Amit"]

for item_number, item_name in enumerate(my_friends):
    print(f"{item_name} is at position #{item_number}")

Here’s an interactive example.

Read and study the following:

  • This answer on StackOverflow about using enumerator to iterate over the items in a list.
  • Read through this discussion on StackOverflow about for-loops and indexes.

A Better Pizza Calculator

The Pizza Calculator assignment had us hard-coding a lot of the values for our tool–values like the costs of each topping. When you scatter values around your script like we did, it makes it very hard to update it later.

After learning about lists and iterators, we can now go back to the Pizza Calculator code we wrote last week and begin refactoring it. Refactoring is the process of changing code, often times in ways that make it run faster, more efficiently, and with greater clarity.

The first step to refactoring is identifying areas where code could be refactored. The first and foremost area is in all these hard-coded relationships between some variable (size, sauce, topping, etc) and its associated cost. Instead of storing them inside the if-elif-else blocks throughout a bunch of functions, we’ll store this data in lists.

Storing data in lists

If we put all our toppings and their costs into lists, we can use the fact that list order remains persistent in Python to craft a more pragmatic way of doing things with our pizza tool.

For example, let’s say I want to get the cost for a small size. Small is associated with the ID ‘S’, and ‘S’ is at position 0 in the list size_ids below:

size_ids = ['S', 'M', 'L']

By storing our variables in a list like this, we accomplish two things: First, we give ourselves a more efficient way of modifying the code later, should the values change (and they most likely will, at least due to inflation); second, we create an opportunity to associate these values with other values in associated lists–lists that contain data related to the data in our list.

Let’s take this one step further. Since I know that ‘S’ is at position 0, if I hold that value in a variable, I can use that variable to access the same position from any other list.

choice = 0
print(size_ids[choice]+" should be 'S'")

But what other lists?

Well, let’s think about this: Python says that lists are guaranteed to have persistent ordering. In other words, the value at position 0 will always be that same value (unless you explicitly change it, of course), and the same is true for the value at position 1, 2, 3, etc.

We have a variable that holds the size IDs–but what about the other elements of a pizza size? Looking at the table provided by the client, we find:

| ID  | Size   | Cost |           Pizza Sizes
+-----|--------+------+
|  L  | Large  | 5.00 |
|  M  | Medium | 4.00 |
|  S  | Small  | 3.00 |

So there are IDs, sizes, and costs. Let’s represent each of them in arrays, making sure that we have the associated data in their respective positions:

size_ids = ['S', 'M', 'L']
size_names = ["Small", "Medium", "Large"]
size_costs = [3.00, 4.00, 5.00]

Do you see how they all line up? For example, the value in size_ids[2] matches the data from size_costs[2]. This is what I mean by their respective positions; the 'S' is in the same position as "Small" and 3.00, and the same goes for 'M' and 'L'.

To help illustrate this, we can declare these variables a little differently:

size_ids =   ['S'    , 'M'     , 'L'    ]
size_names = ["Small", "Medium", "Large"]
size_costs = [ 3.00  ,  4.00   ,  5.00  ]

All I’ve done above is add some spaces in between the values so that you can see the relationship between each list’s position value and the value in the same position from another list.

Let’s look at it one more way:

size_ids = ['S', 'M', 'L']
#            ^    ^     ^
#            |     \     \
#             \     \     +-------+
#              |     +--+         |
#              |        |         |
#              v        v         v
size_names = ["Small", "Medium", "Large"]
#              ^        ^         ^
#              |        |         |
#              |     +--+  +------+
#              |     |     |
#              v     v     v
size_costs = [3.00, 4.00, 5.00]

Are you seeing the relationship?

Going back to our choice variable, let’s see what happens if we set choice to 1 and then print the value of each list’s element at that index:

choice = 1
print(size_ids[choice]) # "M"
print(size_names[choice]) # "Medium"
print(size_costs[choice]) # 4.00

Try this interactive example.

Pretty cool, huh?

Having seen that we can use the same index to get the associated values from multiple lists, we can use the range() function in a for-loop and pass it the length of one of the lists–any of the associated lists will do, since they each should have the same number of elements.

for n in range(len(size_ids)):
  print(f"Size ID: {size_ids[n]}")
  print(f"Size Name: {size_names[n]}")
  print(f"Size Cost: {size_costs[n]}")
  print("---")

The above code will print the following to the console window:

Size ID: S
Size Name: Small
Size Cost: 3.0
---
Size ID: M
Size Name: Medium
Size Cost: 4.0
---
Size ID: L
Size Name: Large
Size Cost: 5.0

Neat!

Now let’s take this one step further. Can we create a function that will automatically use these three lists to print out all available options to the user and then prompt them for a selection?

Why yes, we can!

Take a look at this interactive demo. Run it first, then try to understand what’s happening by studying the code. Pay particular attention to the function called get_index_from_selection().

For the Module 6 assignment, you’ll be using the above demo as a guide to create a new version of the pizza calculator from Module 5.

Happy coding!