I have said we’d go into lists et al. in a later lecture and here we are. Iteration as its name is about iterating. You have an object with multiple (or just one) elements and you cycle over them (or it). And in the last lecture I have mentioned to you that while some objects could be turned into others, some may not. This was–in essence–about whether an object was iterable or not. Lists and sets both have elements and they can be translated into one another. So can tuples. But though this rule doesn’t always work (see: list to string translation is not that easy) but it’s a really good starting point. The iterable object types are: list
; tuple
;str
and a new one for you, dict
.
The while
Loop
So far the names of the “stuff” we saw in python were all in accordance with the job of that “stuff.” And while is no different. while
runs the indented code while a statement is True
. Let me show you with an example:
Example 1
1
2
while True:
print('ikbal')
this will give us
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
ikbal
...
and will not stop ever.
How it works
- The interpreter comes to the
while
statement. - The interpreter checks whether the statement after
while
is true, that is, it checks whetherTrue
isTrue
. True
is true so the interpreter runs the indented part.- After running the indented part once, the interpreter checks again whether the statement is true and it still is so it runs the code again
- The interpreter continues to “check and run” until the statement becomes
False
so in this case, forever. SinceTrue
will never becomeFalse
.
Let’s see another, more extensive example:
Example 2
1
2
3
4
x = 3
while x < 10:
x += 1
print(x)
Output:
1
2
3
4
5
6
7
4
5
6
7
8
9
10
How it works
- The interpreter comes to the
while
statement. - The interpreter checks whether
x < 10
isTrue
. - We defined
x
to be 3 so the statement is True and the interpreter will run the indented code. - The interpreter increases the value of x by 1 and prints the increased value.
- After running the indented part, the interpreter checks whether the statement is still
True
.x == 4
andx < 10
, so it will run the indented part. - This “check-run” process will continue until
x == 10
. Whenx
’s value is 10, the statement will returnFalse
and the interpreter won’t run the indented part.
The for
Loop
The syntax for for
loops is the same with while
. We use these loops when we want to do something with variables in sequences. The syntax is
1
2
for element in iterable:
<do something>
As you see, this is very human-readable. For each element in our iterable, we’re telling the interpreter to “do something.”
Example 1
Let’s try this with a simple list.
1
2
for x in [0, 1, 2]:
print(x)
One thing you should note here is x
. This is just an arbitrary variable we have named. This means for each loop, the element in our list will be assigned to x
. To put it in perspective
Loop | Value of x |
---|---|
1st | 0 |
2nd | 1 |
3rd | 2 |
And now we can run it:
1
2
3
0
1
2
What happened?
- The first element in our list,
0
, gets assigned tox
- The interpreter prints
x
which is no0
- The second element in our list,
1
, gets assigned tox
. x
, which is now1
, gets printed.- The last element in our list,
2
, gets assigned tox
. x
, which is now2
, gets printed.- There are no elements left in our list, so the
for
loop terminates.
Example 2
Let’s make a for loop with strings now.
1
2
for letter in "ikbal":
print(ord(letter))
ord()
: Given a string representing one Unicode character, return an integer representing the Unicode code point of that character. For example, ord('a')
returns the integer 97 and ord(€)
returns 8364. This is the inverse of chr()
. Here’s an ascii
table:
Again, notice how our variable is now letter
. In any case, you can name it whatever you want, but try to use understandable variables as much as you can.
Output:
1
2
3
4
5
105
107
98
97
108
What happened?
- The first letter of
'ikbal'
('i'
) gets assigned to theletter
variable. - The
ascii
code of'i'
is printed. - Repeat step 1 and 2 for the second, third, forth and fifth letter.
- The loop terminates since there are no letters left.
Example 3
Let’s try to make something useful this time: a function that will take two inputs n
and num
and return the positive nth root of num
. Let’s also limit num
to only be positive and of the form answer**n
. For example if n == 2
then num
is a perfect square.
1
2
3
4
5
6
7
8
9
>>> def nth_root(n, num):
...
...
>>> nth_root(2, 9)
3
>>> nth_root(2, 4)
2
>>> nth_root(3, 27)
3
The method will use here is called exhaustive enumeration. It’s basically trial and error. The process is as follows:
- Make an
answer
variable and give it the initial value of1
. - Take our
answer
to the nth power and check if it is equal to ournum
. - If it isn’t, increase the value of our
answer
and repeat the second step until we get our answer.
You can try to implement this on your own before checking the answer.
Starting with the definition line:
1
def nth_root(n, num):
We indent and define our answer
variable. Since our num
is limited to positive perfect nth powers and answer
has to be positive, 1
is a good value to start with.
1
answer = 1
We now need to make a loop which will increase our answer
if answer**n != num
(if the nth power of our current answer
is not equal to num
).
1
2
while answer**n != num:
answer += 1
After this loop completes, answer
variable will be equal to the actual nth root of num
. So we can just return it now.
1
return answer
Let’s test our function a bit.
1
2
3
print(nth_root(3, 27))
print(nth_root(4, 16))
print(nth_root(10, 9765625))
Output:
1
2
3
3
2
5
The smarter way
We know that \(\sqrt[n]{x}=x^{1/n}\) so that’s what we’ll use. (You can also use \(\sqrt[n]{x}=x^{n^{-1}}\) if you want to)
Define our function.
1
2
def smartroot(n, num):
return num**(1/n)
Testing lines:
1
2
3
print(smartroot(3, 27))
print(smartroot(4, 16))
print(smartroot(10, 9765625))
Output:
1
2
3
3.0
2.0
5.000000000000001
Oh! What’s going on? This folks, is why we don’t mess with float
s. Please check What Every Programmer Should Know About Floating-Point Arithmetic and especially this page. Done? No? Alright here’s a quote from the page:
Basic Answers
Why don’t my numbers, like 0.1 + 0.2 add up to a nice round 0.3, and instead I get a weird result like 0.30000000000000004?
Because internally, computers use a format (binary floating-point) that cannot accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already rounded to the nearest number in that format, which results in a small rounding error even before the calculation happens.
Why do computers use such a stupid system?
It’s not stupid, just different. Decimal numbers cannot accurately represent a number like 1/3, so you have to round to something like 0.33 - and you don’t expect 0.33 + 0.33 + 0.33 to add up to 1, either - do you?
Computers use binary numbers because they’re faster at dealing with those, and because for most calculations, a tiny error in the 17th decimal place doesn’t matter at all since the numbers you work with aren’t round (or that precise) anyway.
And here’s a relevant comic:
Example 4
The whole point of this example is to have an excuse to introduce you to the range()
function. OK I lied. Range is not really a function. It’s actually a type on it’s own called range
.
1
2
>>> type(range(5))
<class 'range'>
For now, it’s enough for you to know that range
is much like tuple
s (Remember the un-changeable lists from last lecture?) Also that range creates a sequence with the following properties:
1
range(start, stop, step)
start
: the first number in our range (defaults to 0
) stop
: range stops before this index. (What?!) step
: this is the common difference between elements in our range. (defaults to 1
)
Let’s move on with our example and you’ll understand it more clearly.
1
2
for number in range(0, 5, 1):
print(number)
1
2
3
4
5
0
1
2
3
4
So you see, this is equivalent to using the list [0, 1, 2, 3, 4]
the only difference is that it’s more versatile. In fact
1
2
>>> list(range(0, 5, 1))
[0, 1, 2, 3, 4]
For a sub-example let’s try changing the step.
1
2
for i in range(0, 10, 2):
print(i)
1
2
3
4
5
0
2
4
6
8
Here you can see that the step
is quite literally “the step.” It’s how much our values increase in each new element. There’s one more cool thing we can do with range()
out of the box and that’s reverse ranges.
1
2
for i in range(10, 0, -3):
print(i)
1
2
3
4
10
7
4
1
Another thing that might be apparent is that because of the defaults of start
and step
. You can use range()
without those two and still make it work. (They’ll be added in for you.)
1
2
for i in range(5):
print(i)
1
2
3
4
5
0
1
2
3
4
Why range stops before
Remember how we indexed our elements in a list or string?
1
2
index: 0 1 2 3
List: [0, 1, 2, 3]
So arrays start at 0 (zero). Let’s take the range(5)
for example and turn it into a list:
1
2
>>> list(range(5))
[0, 1, 2, 3, 4]
You remember I said the range stops at this index. And you can see it clearly here. The range stopped before the 5th index which is 5
. And by that, since the range starts at 0
, it stopped before getting to the actual number 5
. Practically, when defining a range
(if you don’t define start
and stop
) you may think that the stop
is equal to the number of integers you get as a result.
It’s, for this reason, better to think that range has a sea of infinite integers that go both ways. And when we define a range
object we’re just taking a slice between our specified indexes.
break
and continue
Like we can make loops, we can also break them and break
does just that.
Example 1
1
2
3
4
5
6
7
x = 0
while True:
print(x)
x += 1
if x == 6:
break
print("Terminated loop.")
1
2
3
4
5
6
0
1
2
3
4
5
Here the interpreter increased x
’s value until x == 6
in which case break
ran. The thing you must take note here is that, the last print()
function didn’t run.
continue
is much like break but what it does is not end the iteration altogether but just skip the current loop.
Example 2
1
2
3
4
for letter in 'string':
if letter == 'r':
continue
print(letter)
1
2
3
4
5
s
t
i
n
g
What happened here was when the loop came to the r
character, the letter == 'r'
condition was fulfilled and the interpreter ran continue
which skipped the letter r
and continued to the letter i
.
Dictionaries
dict
is (not-so-surprisingly) the dictionary object. Dictionaries in python work the same way they do in real life. You have a term and you have a definition. The only difference here is that terms are called keys
and definitions are called values
.
1
2
>>> type({'ikbal': 'pretty'})
<class 'dict'>
Although you don’t need your values and keys to be str
. They can be numbers, lists or anything you want (even custom objects but that’s for later!). And of course like any good dictionary you can have multiple key-value pairs in your dictionary.
1
2
my_dict = {'ikbal': 'pretty',
'elif': 'not pretty'}
The last thing to know about dictionaries is overriding previous pairs. The last definition will always override the first definition you write. For example
1
2
3
4
my_dict = {'ikbal': 'pretty',
'elif': 'not pretty',
'ikbal': 'not pretty',
'elif': 'pretty'}
If we print this list we’ll get
1
{'ikbal': 'not pretty', 'elif': 'pretty'}
We can refer to values in a dictionary with the following syntax:
1
dictionary[key]
Example
1
grades = {'John': 100, 'Jake':85, 'Jolene':60}
If we want to get John’s grade, all we need to do is
1
2
johns_grade = grades['John']
print(johns_grade)
Output:
1
100
Let’s talk about how to iterate over this dictionary of ours. If we want to iterate over just the keys
1
2
3
for key in grades:
...
...
If we want to have both the keys and the values
1
2
3
for key, value in grades.items():
...
...
Slicing and Indexing
We’re finally here. This section will cover all the ways you can manipulate str
, list
, tuple
or what have you.
Indexing
Syntax:
1
object[index]
Yup. That’s all there is to it. Doesn’t matter which object either. If it’s index-able this is they way to do it.
Example 1
1
string = "index me coward. you're only gonna index a string."
Let’s take the letter at the 4th index of this string.
1
2
indexed = string[4]
print(indexed)
Output:
1
'x'
Done!
Example 2
1
lis = [0, 1, 2, 3]
Let’s index this list and get the element at 2nd index.
1
2
indexed = lis[2]
print(indexed)
Output:
1
2
Done!
Tuples are exactly the same as these two and the syntax never changes here, so I’ll pass that.
Slicing
Syntax:
1
object[start:stop:step]
This is much like how we define our ranges.
start
: the first index in our slice (defaults to “the rest”) stop
: range stops before this index. (defaults to “the rest”) step
: this is the common difference between elements in our slice. (defaults to 1
)
Example 1
1
string = "this is a long string me thinks"
Let’s try to take the first word of this string. Our word this
starts at the 0th
index and end before the 4th
index. So that’s what we’ll use. We don’t have to define our step
since that’s 1
by default.
1
2
sliced = string[0:4]
print(sliced)
Output:
1
'this'
Smarter way
There is a string method called split()
that can ease our job immensely here. split()
does just what it says, it splits. The syntax is
1
string.split(which_character_to_split, how_many_times)
By default split()
splits by ' '
(space character) and as many times it can. So for example in our case this would be
1
2
3
>>> splitted = string.split()
>>> print(splitted)
['this', 'is', 'a', 'long', 'string', 'me', 'thinks']
And then we could just get the first element of this list with
1
2
>>> splitted[0]
'this'
Example 2
1
string = "this is a long string me thinks"
Let’s try first get every character before the 6th index and then get every character after and including the 6th index.
1
2
3
4
before6 = string[:6]
after6 = string[6:]
print("Every char < 6th:", before6)
print("Every char >= 6th:", after6)
Output:
1
2
Every char < 6th: this i
Every char >= 6th: s a long string me thinks
Example 3
1
lis = [0, 1, 2, 3, 4, 5, 6]
Let’s get the elements between 1st and 5th indices.
1
2
sliced = lis[2:5]
print(sliced)
Notice how we don’t want the 1st index so we define start
as 2.
Output:
1
[2, 3, 4]
Example 4
1
tup = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
Let’s get the every 5th element in this tuple
1
2
every5th = tup[::5]
print(every5th)
Notice here how we used the defaults for start
and stop
which were “the rest.” This will ensure that we start at the beginning of our tuple and end at the end.
Output:
1
(0, 5, 10)
One last note about iterable types
This part is to introduce you to the len()
(length) function. It simply gives us how many elements there are in an iterable.
1
2
3
4
>>> len([0, 1, 2, 3, 4, 5])
6
>>> len('string')
6