In this lesson we cover the concept of iteration, which is basically the idea of repeating the same set of instructions until a certain condition is met. This sequence of instructions is also called a loop.
This is an extremely important and powerful concept, as it allows us to finally automate tasks! Remember that a fundamental feature of programmers is that they are lazy (refresh Larry Wall's "Three Virtues" that we saw in the first notebook!). The more you can delegate to the machine and avoid repeating boring and repetitive tasks yourself, the better!
Of course, as the fantastic Randall Munroe likes to remind us, reality sometimes challenges this idea...
The two most common ways to iterate (or repeat) a set of commands are the while loop and the for loop. That's why in Python the words while
, for
(and in
) are reserved words and you can't use them for variable names.
The video below is a nice (if slightly scary) intro to each of the approaches; you might remember them the next time you're in the gym:
The 'right' approach you use usually depends on what sort of data structure you are iterating over and whether we know before we start how many times we want to repeat the commands. That might sound a little vague at this point, but come back and have a think about it once you have a better understanding of how loops work.
So let's first see how while loops work, then we'll look at actual loops in Python.
Remember if
statements? When the Python interpreter finds an if
statement in your code, it checks if the specified condition evalutes to True
. If the condition is True
then it runs the remainder of the indented code block following the if
. The 'true' block of code is only run once.
Using the while
statement, the 'true' block is run for as long as the condition evalutes to True
. So if the statment continues to evaluation to True
then the block of code is run again and again and again and again... and again until some stopping condition is reached (usually the while condition becomes False
for 'some reason').
This allows us to finally do some interesting stuff in code:
In [ ]:
counter = 1 # Starting condition: counter is at 1
print("The while loop is ready to begin.")
while counter <= 10: # Test the condition
print("The counter is at: " + str(counter))
counter += 1 # Increment the counter
print("The while loop has ended.")
If that looks confusing, don't worry! It's perfectly normal as that's your first example of iteration.
Let's take a deeper look:
counter
and we initialised it with a value of 1. while
statement to check if the value of counter
was less than or equal to 10. True
, we printed the value of counter
and then added 1 to the counter
. while
statement returned a False
(this happened because the value of counter went all the way to 11).while
block (i.e. the last line of non-indented code)The fundamental idea is thus: while this condition holds, repeat the instructions in the indented code block.
Many beginner programmers get confused by the fact that the value of counter
is increasing without the code advancing to the point where in prints out 'The while loop has ended'. Remeber: we are starting the instruction from the beginning of the while block. So the first time that we hit it, counter
has value of 1 because this is what we set it to outside of the while loop.
But the second time the while conditional is evaluted, counter
has been incremented to 2 because the last line of the while
block is counter += 1
! So we increased its value while inside the while block. At the beginning of the third iteration it will have a value of 3, while at its end it will be incremented to 4. And so on...
CAVEAT: pay attention to how you write your while
loops! They can potentially run forever (for as long as the condition they are evaluating is True
) maxing out your machine's memory. For example:
# don't run this!
# or if you do, save everything first
# and then be prepared to stop the code execution manually
# (usually by pressing CTRL+D or Cmd+D)
# in the terminale/console
while True:
print("Forever Loop! Yeeee!")
In [ ]:
otherCounter = 1
while otherCounter <= 10:
print("This is an odd number: " + str(otherCounter))
otherCounter += 2
You can also run while
loops decrementally until they meet a certain condition (run the code block to see how):
In [ ]:
myThirdCounter = 10
while myThirdCounter >= 0:
print("Decrementing counter: " + str(myThirdCounter))
myThirdCounter -= 1
In [ ]:
myFourthCounter = 1
while myFourthCounter < 10:
print(str(myFourthCounter))
myFourthCounter += 1
if myFourthCounter == 5:
print("Time to escape this madness!")
break
That last example shows that you can 'nest' an if (if myFourthCounter
) inside of a while
loop -- this approach allows us to add much more complex logic to our code than we could before. Here's an example, but see if you can figure out what it will print out (and how) before you run the code block!
In [ ]:
i=0
while True:
i += 1
if i % 2 != 0:
print( str(i) + " is odd.")
if i == 9:
print("\t i is 9")
else:
print("\t i is not 9")
else:
print( str(i) + " is even.")
if i % 4 == 0:
print("\t i is divisible by 4.")
elif i == 8:
print("\t i is 8")
if i == 21:
break
In terms of explanation:
Python is going to keep running the code (while True
) until it is told to stop by a break
(which happens at i == 21
). In fact, if you remove the break
then you will crash jupyter because the computer will print out every even number to infinity (which the computer can’t handle because it runs out of memory).
'Inside' the while
loop there is a main if
/else
block:
if i % 2 != 0:
... do something with odd numbers...
else:
... do something else with even numbers...
'Inside' the odd numbers section we now have a second if
/else
block:
if i == 9:
... do something if the odd number is 9...
else:
... do something else if the odd number is not 9...
And it’s a similar story in the even numbers section:
if i % 4 == 0:
... do something if the even number is divisible by 4...
elif i == 8
... do something else if the even number is 8...
We'll not give you the answer to how to print out WOOT
in place of 7 below, but at this point you all the clues you need. It’s the concept that is the hard part and following what’s going on when you start to nest conditions inside of conditions. We can talk this through more if anyone needs more help getting to grips with this!
i
and j
in LoopsIf you want to skip to the next iteration then you can continue the exection of a while
loop using the continue
statement. In the following example we are going to skip all even numbers and print WOOT!
if we hit a lucky 7 (or any number divisible by 7). We'll break
out of the loop after hitting the 21st iteration.
One other thing to notice is that we've switched from looooong counter names like myFourthCounter
to i
. A common programming trick (which is well-known and so actually increases the legibility of your code to others) is to use i
and j
for counters in loops (you can add k
if you ever need a third level loop).
In [ ]:
# So use i and j as counters because
# this is a stylistic convention and
# helps you to to write more concise code
# (and be constructively lazy).
i = 0
while True:
i += 1
if i % 2 != 0:
print(i)
else:
continue
if i % 7 == 0:
print("WOOT!")
if i == 25:
break
print("Done!")
How would you change the code above so that it printed only the odd number or 'WOOT!', but not both? In other words, change the code so that it prints:
1
3
5
WOOT!
9
11
13
15
17
19
WOOT!
In [ ]:
i = 0
while True:
i += 1
if i % 2 != 0:
if i % 7 == 0:
print("WOOT!")
else:
print(i)
if i == 25:
break
print("Done!")
In [ ]:
i = 0
while True:
i += 1
if i % 2 != 0:
continue
if i == 22:
break
print(i)
print("Done!")
In [ ]:
# remember our friends, the british computer scientists?
britishCompList = ["babbage", "lovelace", "turing"]
# this is the condition python is going to check against
stoppingCondition = len(britishCompList)
counter = 1
while counter < stoppingCondition:
print(britishCompList[counter] + " was a british computer scientist")
# don't forget to increment the counter!!!
counter += 1
Wow, lot of stuff in that chunk of code, eh? Well, once again, take a deep breath and go through it line by line.
The important bits are:
len
of britishCompList
as stopping condition, instead of specifying ourselves a number.counter
, as at each iteration counter
assumes the value of 0, 1 ... until the stopping condition is met. This is equivalent to writing :print(britishCompList[0]) # on the first iteration
print(britishCompList[1]) # on the second iteration
But wait a second... what about the great Babbage? Why isn't his name displayed? Certainly not because he's not worth a mention! Can you spot the reason why the iteration skipped him?
Hint: check (using print
) the values of counter
and britishCompList
. What is the condition we are asking Python to use?
In [ ]:
counter = 0
while counter < len(britishCompList):
print(britishCompList[counter] + " was a british computer scientist")
# don't forget to increment the counter!!!
counter += 1
If you think that being skipped over in the loop above was tough for old Babbage, then perhaps you might be interested to hear about the history of the NASA 'computers' who helped put a man on the moon and are only getting a film made about them in 2016.
In [ ]:
counter = 0
nonBritishProgrammers = ["Torvald", "Knuth", "Swartz"]
stoppingCondition = len(nonBritishProgrammers)
while counter < stoppingCondition :
print("This is a computer genius too: " + nonBritishProgrammers[counter])
# always remember to increment the counter!!!
counter += 1
CAVEAT: An important condition to remember when iterating over a list is thus that lists are zero-indexed! If if you start you counter from 1 you will certainly miss the first item in the list (which has an index of 0).
But watch out! There's more:
In [ ]:
counter = 0
nonBritishProgrammers = ["Torvald", "Knuth", "Swartz"]
stoppingCondition = len(nonBritishProgrammers) -1
while counter <= stoppingCondition :
print("These are geniuses too! " + nonBritishProgrammers[counter])
# always remember to increment the counter!!!
counter +=1
We've just seen that we can use a while
loop to iterate over a list, but it's kind of clunky and inelegant. All those counters and things makes for a lot of extra typing, especially when you consider how terse Python's language usually is! Surely there must be another way?
You guessed right, my friend! Let me introduce you to the 'for ... in:'
statement:
In [ ]:
for programmer in britishCompList:
print(programmer)
As you can see, the for loop statement is much more concise: you simply tell Python to repeat a certain instruction (print the list item in this example) for EVERY ITEM in A SEQUENCE. The sequence here is the list of British computer scientists britishCompList
created in the code block above.
Now, Python will stop automatically when the sequence is finished without you having to worry about specifying the stopping condition (which you have to do when using a while
loop).
Notice also that we didn't have to initialise the counter
value!
So, the biggest difference between a while
and a for
loop is thus not merely stylistic, it's also conceptual!
Let's recap with another example:
In [ ]:
# WHILE LOOP
whileCounter = 0
myList = [0,1,2,3,4]
stoppingCondition = len(myList)
while whileCounter < stoppingCondition:
print("Element number: " + str(myList[whileCounter]))
whileCounter +=1
In [ ]:
# FOR LOOP
for element in myList:
print("Element number: " + str(element))
SIDENOTE: See how the value of myList[whileCounter]
and that of element
in the two loops are the same? That's because Python is doing the indexing job for you behind the scenes.
In [ ]:
numbers = [1,2,3,4,5,6,7,8,9,10]
for n in numbers:
if (n % 2 != 0):
print(n)
Now, as we are lazy programmers, let's repeat the above example combining the range
function with a for
loop. It will save us the hassle of typing all those numbers!
In [ ]:
for i in range(10):
if (i % 2 != 0):
print(i)
Cool, we have seen how to iterate over a list, but what about a dictionary? Well, if you remember we said that you might think of a dictionary as a kind of list where each element isn't indexed by an integer, but rather by a unique identifier (key).
Hence, as with lists where we iterate over the indexes, with dictionaries we are going to iterate over the keys!
In [ ]:
programmers = {
"Charles": "Babbage",
"Ada": "Lovelace",
"Alan":"Turing"
}
for k in programmers:
print(k)
NOTE: I've used the variable k
. This is simply an arbitrary word that I've choosen and not some kind of special variable. You could have used anyRandomNameForWhatMatters
.
What if you want to retrieve the values? In that case you should use not only two variables, (the first for the keys and the second for the values) but also invoke the method items()
on the dictionary, like so:
In [ ]:
for k,v in programmers.items():
print("this is the value: \'" + v + "\' for the key: \'" + k +"\'")
In [ ]:
KCL_marker = {
"type": "Feature",
"properties": {
"marker-color": "#7e7e7e",
"marker-size": "medium",
"marker-symbol": "",
"name": "KCL"
},
"geometry": {
"type": "Point",
"coordinates": [
-0.11630058288574219,
51.51135999349117
]
}
}
for k,v in KCL_marker["properties"].items():
print("KCL_marker has a property: '" + v + "' for the key: " + k)
Very good! Let's summarise some facts about loops:
The geo-excercise I'll give you this time is a real-world problem that you might face one day in your career as geospatial professionals. In principle it is possible to work out everything below based on what we've done up to this point; however, this exercise is also hard since it is a big jump conceptually that mixes up the ideas in a new way. So if you can't quite make out the answer don't worry, just try to understand the concepts above and see if you can solve parts of the problem.
Let's say a colleague of yours used a GPS to survey at regular intervals the dispersion of pollutants in a patch of terrain. Unfortunately, after a good start they forgot to record all the remaining points!
But that's not a terrible problem, as the transect has a perfect West-East orientation and direction, and all the points are spaced by a the same value dX (short for 'delta-X', the change in X between each point) of 0.03 degrees longitude, i.e.:
(0.0102, 51.592)-----(X+dX,Y)-----(X+2dX,Y)-----(X+3dX,Y)--->
Using what we've seen so far, try to create a GeoJSON featureCollection
of points. To give you a head start, I've provided some scaffolding.
HINT: Being the skilled geographer that you are, you immediately realise that actually you've got all the coordinates that you need, even for the missing points (i.e. the latitude values will remain constant...)
In [ ]:
# define a new featureCollection: it is basically a very fancy dictionary
# to which we are going to add new 'features' (which are points on a map
# but represented as *data* by a dictionary). We need to add one feature
# at a time when building our transect...
# initial coordinate list
init_coords = [-0.0200, 51.592]
# dX delta
dx = 0.03
gap = 0
transect = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": init_coords
}
}
# -------------------------------------------------------------
# here is where the remaining three points have to be
# added using *code* and not manually
# -------------------------------------------------------------
]
}
# new empty list where I'm going to put all the new dictionaries
# a.k.a. all the new points
three_new_points = []
for i in range(3):
# define a new point
new_point = {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": []
}
}
# increment the longitude
gap += dx
# create a new list with the updated coordinates
new_coordinates = [init_coords[0] + gap, init_coords[1]]
# assign the new coordinates to the coordinates key
# in the new point dictionary
new_point["geometry"]["coordinates"] = new_coordinates
new_point["properties"]["name"] = "Point " + str(i+1)
# append the new point dictionary to the list of new points
three_new_points.append(new_point)
# append to the feature list the three new points
# that we created
transect["features"].extend(three_new_points)
print(transect)
This output on its own makes very little sense, but it's actually a real world data structure called JSON. Below we use a handy library (that you might not yet have installed on your computer) to turn that JSON into something easier to read, and then we'll see how you can use it!
In [ ]:
import json
parsed = json.loads(str(transect).replace("\'", "\""))
print(json.dumps(parsed, indent=4))
In [ ]:
# And show the points on an interactive map!
from IPython.display import GeoJSON
GeoJSON(parsed)
The following individuals have contributed to these teaching materials:
The content and structure of this teaching project itself is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 license, and the contributing source code is licensed under The MIT License.
Supported by the Royal Geographical Society (with the Institute of British Geographers) with a Ray Y Gildea Jr Award.
This notebook may depend on the following libraries: None