Investing in Indexes

Last time, we talked about arrays. An array is a single variable which can store many different values. Each value is accessed by a numeric index.

We used an example tracking times and distances in road races, and ended with the following pseudo-code (which admittedly is almost, but not quite, entirely unlike Python):

race_distance = array()
race_time = array()
race_average = array()

race_distance[1] = 5    # This was a 5K
race_time[1] = 30.50    # Time in seconds, so this is 00:30:30
race_average[1] = race_time[1] / race_distance[1]

race_distance[2] = 10   # This was a 10K
race_time[2] = 65.25    # Time in seconds, so this is 01:05:15
race_average[2] = race_time[2] / race_distance[2]

While this helps keep our data in one place, you still have to type the numbers for the index, and calculate the race average for each.

Let’s see if we can make our lives a little easier.

Data Gathering

Before we start, let’s make one minor change to the code above.

Normally, you wouldn’t have specific race distances and time hard-coded as shown above. You would get that data either from the user or some other data source. So let’s rewrite our pseudo-code to gather input from the user rather than simply enter in hard-coded values for everything:

race_distance = array()
race_time = array()
race_average = array()

race_distance[1] = input("Enter distance: ")  # Get distance
race_time[1] = input("Enter time: ")          # Get time
race_average[1] = race_time[1] / race_distance[1]

race_distance[2] = input("Enter distance: ")  # Get distance
race_time[2] = input("Enter time: ")          # Get time
race_average[2] = race_time[2] / race_distance[2]

Using input() means the user will now have to enter the distance and time for each race.

Incremental improvement

Last time, we mentioned that the index of an array was just a number. It makes sense, then, that we could use a numeric variable instead of number there.

Let’s rewrite the code with that idea in mind:

race_distance = array()
race_time = array()
race_average = array()
index = 1

race_distance[index] = input("Enter distance: ")  # Get distance
race_time[index] = input("Enter time: ")          # Get time
race_average[index] = race_time[index] / race_distance[index]

index = index + 1

race_distance[index] = input("Enter distance: ")  # Get distance
race_time[index] = input("Enter time: ")          # Get time
race_average[index] = race_time[index] / race_distance[index]

Instead of using the numbers 1 and 2, we use the variable index to select the proper item in the array. It is first set to the value of 1 before the first set of data is input.

The line index = index + 1 increments the value of index by taking it’s current value, adding 1, then assigning the result back to index. The effect is to give index the value of 2 before the next set of data is gathered.

But why increment index rather than simply write index = 2? Our next idea will explain that.

I Repeat… I Repeat… I Repeat…

The code above really isn’t better yet. In fact, it’s more complicated now because of the new variable index. Stay with me — it will get better. In fact, the two changes we’ve introduced are the keys to making everything better.

Incrementing index is part of the answer. The line index = index + 1 will always make index one larger than it was before. It doesn’t matter what value it has, index = index + 1 will always change index to the next value.

For example, what if we added the following after the code above:

index = index + 1

race_distance[index] = input("Enter distance: ")  # Get distance
race_time[index] = input("Enter time: ")          # Get time
race_average[index] = race_time[index] / race_distance[index]

What is the value of index now? If you said 3, great, but calm down — you’re right, but it’s just math. We can do this as many times as we want, and index will always get a new value one higher than it was before. That ensures that every new set of data the user inputs will always be stored at a new index in the race_distance and race_time arrays.

The other part of the answer lies with the pattern of code we’ve been writing. We’ve written the same four lines of code three times now:

race_distance[index] = input("Enter distance: ")  # Get distance
race_time[index] = input("Enter time: ")          # Get time
race_average[index] = race_time[index] / race_distance[index]

index = index + 1

Writing the same code multiple times is something to be avoided when programming. Every time you write the same code, you have a chance to introduce a new bug.

What you want is a way to write the code once, and run it multiple times. What you want is a loop.

You Spin Me Right Round

Loops allow code to be written once, and executed repeatedly. Loops are extremely common in programming. If you’ve ever played a video game, you’ve experienced one first-hand — all games use a game loop to process your input and update the screen.

There are a few different flavors of loops, but that all have a few things in common:

  • Before the loop starts, there is normally some initialization which occurs.
  • The body of the loop contains the code to be executed.
  • Loops end either when a condition is met, or when a certain number of iterations have been complete.

An iteration occurs every time the loop runs the code. By counting the number of iterations, you ensure the loop only runs as long as necessary.

Conceptually, a loop looks something like this:

A loop as a flowchart

For our code, the condition will be that the loop has run three times, which means we need to count the number of loop iterations. Our code technically has a loop counter already. The variable index is counting how many race distances and times we are collecting. It’s just not in a loop yet, so let’s fix that.

Like a Record, Baby

Take a look at the code, and break it into the parts of the loop we need:

  • The code has some initialization at the top which is only run once. Because of this, it won’t be part of the loop.
  • The body of the loop is what gets executed multiple times, so we’ll put that repeated code inside the loop.
  • The loop ends when the user has entered three times and distances.

Here’s the whole thing rewritten as a pseudo-code loop:

# INITIALIZATION
race_distance = array()
race_time = array()
race_average = array()
index = 1

# Loop starts here
loop:
    # Loop body
    race_distance[index] = input("Enter distance: ") # Get distance
    race_time[index] = input("Enter time: ")         # Get time
    race_average[index] = race_time[index] / race_distance[index]

    index = index + 1
# Check to end loop here
until index > 3

Wait, why is the check until index > 3 rather than until index = 3?

Think about the value if index as it goes through the loop:

  • The first time through the loop, index = 1 as the loop begins, but is set to index = 2 just before the check at the bottom.
  • The second time through the loop, index = 2 and is then set to index = 3 just before the check at the bottom.

If the check was until index = 3, the loop would end after only these two iterations. By setting it to until index > 3, we ensure the loop executes at least three times.

Summary

Astute readers will note that the original code (with three separate race times and distances) was twelve lines of code, while the new loop version is only ten lines (minus the blanks and comment lines) — not a lot of savings there. So why go through all this trouble?

What if you needed to get data on five races? Or ten? Or a hundred? Or maybe you don’t know how many races you need? These same ten lines of code can be used to gather race data in all these cases, with only a small change to the condition at the end of the loop.

So where is all this data going? How does the computer know where to put the data when it’s input(), and how to find it again when computing the race_average? We’ll talk about that next time…