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 lesson!). 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…
Two Ways to Iterate
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:
So let’s first see how while loops work, then we’ll look at actual loops in Python.
WHILE Loops
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 run 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:
counter =1# Starting condition: counter is at 1print("The while loop is ready to begin.")while counter <=10: # Test the conditionprint("The counter is at: "+str(counter)) counter +=1# Increment the counterprint("The while loop has ended.")
The while loop is ready to begin.
The counter is at: 1
The counter is at: 2
The counter is at: 3
The counter is at: 4
The counter is at: 5
The counter is at: 6
The counter is at: 7
The counter is at: 8
The counter is at: 9
The counter is at: 10
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:
First, we defined a variable counter and we initialised it with a value of 1.
Then we used the while statement to check if the value of counter was less than or equal to 10.
Since that condition evaluated to True, we printed the value of counter and then added 1 to the counter.
The indendented block of code was then run again and again by Python until the while statement returned a False (this happened because the value of counter went all the way to 11).
After that Python simply continued to execute the code outside the whileblock (i.e. the last line of non-indented code)
The fundamental idea is:
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’. Remember: we are starting the instruction from the beginning of the while block. So the first time that we hit it, counter has a 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 ‘inside’ the indented 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 whileloops! 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/consolewhileTrue:print("Forever Loop! Yeeee!")
A challenge for you!
Complete the code below to run a while loop that prints only odd numbers under 10.
To interrupt the execution of a while loop you can use the break statement
myFourthCounter =1while myFourthCounter <10:print(str(myFourthCounter)) myFourthCounter +=1if myFourthCounter ==5:print("Time to escape this madness!")break
1
2
3
4
Time to escape this madness!
Nesting Conditions
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 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 mainif/else block:
if i %2!=0: ... do something with odd numbers...else: ... do something elsewith even numbers...
‘Inside’ the odd numbers section we now have a second if/else block:
if i ==9: ... do something if the odd number is9...else: ... do something elseif the odd number isnot9...
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 elseif the even number is8...
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 Loops
If 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).
# 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 =0whileTrue: i +=1if i %2!=0:print(i)else:continueif i %7==0:print("WOOT!")if i ==25:breakprint("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:
i =0whileTrue: i +=1if i %2!=0:continueif i ==22:breakprint(i)print("Done!")
2
4
6
8
10
12
14
16
18
20
Done!
Iterating over a List
What you just saw with the while statement is a way of iterating: a way of repeating a certain set of instruction until a given condition is met. We can use to our advantage not only to print stuff, but also to ‘iterate over’ the elements in a list:
# remember our friends, the british computer scientists?britishCompList = ["babbage", "lovelace", "turing"]# this is the condition python is going to check againststoppingCondition =len(britishCompList)counter =1while counter < stoppingCondition:print(britishCompList[counter] +" was a british computer scientist")# don't forget to increment the counter!!! counter +=1
lovelace was a british computer scientist
turing was a british computer scientist
Wow, a lot going there eh? Once again, go through the code line by line until it makes sense. The important bits are:
Notice that we used the len of britishCompList as stopping condition, instead of specifying a number directly.
We accessed the items in the list with a regular index, like we have done in the past.
The difference is that this time the index was the variable counter since, on each iteration counter assumes the value of 1, 2, … until the stopping condition is met.
This is equivalent to writing :
print(britishCompList[1] +" was a british computer scientist") # on the first iterationprint(britishCompList[2] +" was a british computer scientist") # on the second iteration
lovelace was a british computer scientist
turing was a british computer scientist
A challenge for you!
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? Can you fix the code to include Babbage?
Hint: check (using print) the values of counter and britishCompList. What is the condition we are asking Python to use?
counter =0# To include Babbage, we need to start counter from 0while counter <len(britishCompList):print(britishCompList[counter] +" was a british computer scientist")# don't forget to increment the counter!!! counter +=1
babbage was a british computer scientist
lovelace was a british computer scientist
turing was a british computer scientist
Other Overlooked Computers
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.
counter =0nonBritishProgrammers = ["Torvald", "Knuth", "Swartz"]stoppingCondition =len(nonBritishProgrammers)while counter < stoppingCondition :print("This is a computer pioneer too: "+ nonBritishProgrammers[counter])# always remember to increment the counter!!! counter +=1
This is a computer pioneer too: Torvald
This is a computer pioneer too: Knuth
This is a computer pioneer too: Swartz
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:
Another challenge for you!
Can you guess why I needed to subtract -1 to the list’s len?
[Hint: Check the condition again. Is the same as before? (Run the code below before continuing)]
We can see that from the code above the while condition is slightly different: while counter < stoppingCondition : versus while counter <= stoppingCondition. Because len counts the number of elements, if we use <= we will access the variable counter at each iteration counter with the value of 0, 1, 2, 3. This would result in an indexError since we only have three variables.
FOR Loop
We’ve just seen that we can use a while loop to iterate over a list, but it seems kind of clunky and inelegant when we already ‘know’ how many items are in a list. All those counters and things makes for a lot of extra typing… surely there must be another way?
You guessed right, my friend! Let me introduce you to the 'for ... in:' statement:
for programmer in britishCompList:print(programmer)
babbage
lovelace
turing
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!
Element number: 0
Element number: 1
Element number: 2
Element number: 3
Element number: 4
myList = [0,1,2,3,4]# FOR LOOPfor element in myList:print("Element number: "+str(element))
Element number: 0
Element number: 1
Element number: 2
Element number: 3
Element number: 4
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.
A challenge for you!
Print only the odd numbers in the list. HINT: remember the modulo operator?
numbers = [1,2,3,4,5,6,7,8,9,10]for n in numbers:if (n %2!=0):print(n)
1
3
5
7
9
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!
for i inrange(10):if (i %2!=0):print(i)
1
3
5
7
9
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!
programmers = {"Charles": "Babbage","Ada": "Lovelace","Alan":"Turing"}for k in programmers:print(k)
Charles
Ada
Alan
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:
for k,v in programmers.items():print("The value '"+ v +"' has the key '"+ k +"'")
The value 'Babbage' has the key 'Charles'
The value 'Lovelace' has the key 'Ada'
The value 'Turing' has the key 'Alan'
A Challenge for you!
Iterate over the GeoJSON marker and print its “properties”.
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 marker["properties"].items():print("Marker has property '"+ v +"' for key "+ k)
Marker has property '#7e7e7e' for key marker-color
Marker has property 'medium' for key marker-size
Marker has property '' for key marker-symbol
Marker has property 'KCL' for key name
Very good! Let’s summarise some facts about loops:
you can increment the counter
but also decrement it (effectively counting down!)
the increment doesn’t need to be 1 every the time (you can increment by 2, 50, whatever..)
don’t forget to to indent the block of code after the colon!
Applied Geo-example
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’ or \(\delta x\), the change in X between each point) of 0.03 degrees longitude, i.e.:
Using what we’ve seen so far, we’re going to try to create a featureCollection: that is, a group (collection) of points (features). To give you a head start, I’ve provided some scaffolding.
Tip
Being the skilled geographer that you are, you immediately realise that actually you’ve got all the information that you need, even for the missing points (i.e. the latitude values will remain constant…)
# 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 listinit_coords = [-0.0200, 51.592]# dX / delta-X dx =0.03gap =0transect = {"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* below and not manually# ------------------------------------------------------------- ]}# new empty list where we're going to put all the new dictionaries # a.k.a. all the new pointsthree_new_points = []for i inrange(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 createdtransect["features"].extend(three_new_points)
This output on its own makes very little sense since it’s so hard to read, but below we can use a handy library to turn that GeoJSON into something easier to read.