Be sure to do all exercises and run all completed code cells.

If anything goes wrong, restart the kernel (in the menubar, select Kernel\(\rightarrow\)Restart).


Lists, Iteration and for Loops

Slicing Sections out of Strings

Characters in a string can be identified from their position using their index.

  • Numbering in Python starts from zero.

  • This can be thought of as numbering the positions between the letters:

| W | o | r | d | ! |
0   1   2   3   4   5

We can obtain more than one letter by slicing it using a range of indices: string[index1:index2], where the slice is all letters between the two indices (see diagram above).

a = "Word!"

print(a[0:4])
  • empty indices: If we leave either of the two positions (index1 or index2) blank:
    [:] means “from the start to the end”
    [1:] means “from position 1 to the end”
    [:3] means “from the start to position 3”

  • Use index slicing to cut out and print the sections of the string indicated by the Expected output in the comments:

a = "Word!"

#print(a[?:?])
# YOUR CODE HERE

#Expected output > W
a = "Word!"

#print(a[?])


# YOUR CODE HERE

#Expected output > Wor
a = "Word!"

#print(?)
# YOUR CODE HERE

#Expected output > d!

Now study the example below:

print(a[:3]+a[3:])

Accessing a character in a string using its Index

Accessing a single character in a string can be simplified using just first the index (i.e. before the letter).

This is done using the string name with the index number in square brackets string[index].

a = "Word!"
print(a[0])
  • Now do the same using the indices 1,2,3,4,5 in turn:

    • re-run the same cell each time with a different index:

#print(a[?])
# YOUR CODE HERE
  • Note that we cannot access indices beyond the length of the character string held by the variable a, trying this produces an error.

Negative Indexing

Negative numbers can be used to start from the end of the string and work backwards:

 | B | a | c | k | w | a | r | d | s |
-9  -8  -7  -6  -5  -4  -3  -2  -1
b = "Backwards"
print(b[-5:-1])
# Slice the word Back using negative indexing
print(b[:-5])
# Slice the word wards using negative indexing
print(b[-5:])
# make note of this example and later think of how to automate it using loops...
print(b[-1] + b[-2] + b[-3] + b[-4] + b[-5] + b[-6] + b[-7] + b[-8] + b[-9])

A third parameter can be given to the slice notation specifying the step-size:
START:STOP:STEP
e.g.:

print(b[0:9:1])
print(b[0:9:2])
print(b[::3])
print(b[::-1])

Lists

A list is a data type consisting of multiple elements.

Lists are defined by enclosing them in square brackets.
The elements in a list can be of any type in Python, including text strings or numbers.

v = [1, 2, 1.5, "Hello"]
print(v)

Accessing an item in a list using its Index

The elements from a list can also be selected using their index in square brackets following the list’s name, just like with strings:

import math

v1 = [1, 2.5, "Hello", "goodbye", math.pi]
# access the first element
print(v1[0])
# slice from between the second and fourth "fence-post" (third and fourth elements)
print(v1[2:4])
# print the last element
print(v1[-1])

Slicing Lists

We can also slice lists to take more than one value at a time.

pilist = [3, ".", 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9]

print(pilist[0:7])

Joining Lists

Concatenating (joining) lists can be done using the + symbol, which does not add the lists in a numerical sense.

a = [1, 2, 3, 4, 5]
b = [6, 7, 8, 9, 10]
c = a+b

print(c)

Nested Lists

Lists can also contain lists. These are known as nested lists and are demonstrated below:

lst1 = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
lst2 = [[1, 2, 3], lst1, "Hello"]

print(lst2)
ls1 = [1, 2, 3]
ls2 = [4, 5, 6]
ls3 = [7, 8, 9]
lst = [ls1, ls2, ls3]

print(lst)

Changing Values in a List

Any entry in a list can be changed using its index or slice:

lst1 = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]

# replacing a single element print lst1
lst1[0] = "A"
print(lst1)
# replacing slice ["d","e","f","g"] with [3,4,5]
lst1[3:7] = [3, 4, 5]
print(lst1)
  • Replace element “b” with the list [7,8,9]:

lst1 = ['A', 'b', 'c', 3, 4, 5, 'h', 'i', 'j']

#lst1[?] = ?
# YOUR CODE HERE

print(lst1)

Expected Result: ['A', [7, 8, 9], 'c', 3, 4, 5, 'h', 'i', 'j']

Click for solution

Notice the different behaviour when replacing a slice or a single element with a list:

  • a slice is replaced with elements from the inserted list in place of the original slice

  • a single entry has the list inserted with the list as the element entry.

Notice also that the length of the list changes as we replace a 4 element slice with a three slice. Also when counting list entries, the entries in sub-lists do not get counted, just the sub-list itself as a whole object.

Accessing Sub-items of Lists

Look at the following example and see if you can figure out what’s going on (explanation beneath):

lst1 = [1, 1.5, "hello", [1, 2, 3, 4]]

a = lst1[3]
print(a)
print(a[1])

print(lst1[2][1])
print(lst1[3][2:4])

The basic idea is that the first set of square brackets says which part of the main list we want.

If this item itself has more than one element we can access these elements
using another set of square brackets: listname[item][subitem][subsubitem] etc.

The first example above shows this more clearly by firstly extracting item lst1[3] of the main list and then taking element 1 from this. The next example shows this on a numerical list.

lst = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

print(lst[1])
print(lst[1][0])

a = lst[1][2]
b = lst[2][0]

print(a*b)

Summary

# a comment is ignored

something = "A string of characters"  # giving a text string a variable name
# slicing: remember it goes up to one less than the last index
print(something[2:8])

# accessing using an index number: remember indexing starts at zero
print(something[3])

a_list = ["anything in", 2, "square", "brackets", [1, 2, 3]]

print(a_list[1] * a_list[4][2])  # accessing lists and sub-lists

Generating New Lists

Using inbuilt functions

The list() function can be used to create a new empty list:

a = list()
print(a)

This is the same as saying:

a = []

Converting an iterable object to a list:

a = "Hello World" # this is a string - it is iterable since we can slice it or iterate over its elements
b = list(a) # make a list from the string contents

print(b)

Adding to (“appending” to) a list:

b = list("Hello World")

b.append("!")

print(b)
"-".join(b) #join the elements of a list using the specified string between them

We can also combine lists using the + operator:

a = [1,2,3]
b = [4,5,6]
c = a+b

print(c)
a = ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd']
b = a+["!"]

print(b)

The function list() makes a list out of the elements of an object, and range() creates a range of numbers to iterate over:

# create a list with a range of 10 elements
list(range(10))
# Numbers don't have to start at zero:
list(range(5,10))
# a third parameter controls the step size (integers only)
list(range(1,10,2))
  • Make a list from 3 to 9 in steps of 3:

#a = ?
# YOUR CODE HERE


print(a)

Result: [3, 6, 9]

Click for solution

Making a new list from another list

New lists can be generated by manipulating each of the items in an existing list.

  • For example “print a list of \(x^2\) values for all \(x\) values in the list [1, 2, 3]”.

xlist = [1, 2, 3, 4, 5]

xsq = [x**2 for x in xlist]
print(xsq)

This method is known as “list comprehension”.

This also works for non-numerical lists:

# apply the function len(a) for every word `a` in the list [...]
word_lengths = [len(word) for word in ["spam", "egg", "cheese"]]

print("The words in the list are:", word_lengths, "letters long.")

And for strings:

mystring = "Hello World!"

# create a new list with elemets that are the string "The next..." plus each character c in the given list
newlist = ["The next character is "+c for c in mystring]

print(newlist)

Exercise 1:

  1. Use the list() and range() functions to create a list of integers called ivals

  2. Use list comprehension on ivals to create a list with 9 values from \(-\pi\) to \(\pi\).

    • The formula to use is: \(-\pi + (2 n \pi)/8\) for each value \(n\) in a list [0...8] created using list() and range().
      Use the syntax: newlist = [CALCULATION for VALUE in OLDLIST] using the calculation formula above

  • You will need to import pi from the math library

  • The values obtained should be \(-\pi, -3\pi/4, -\pi/2, -\pi/4, 0.0, \pi/4, \pi/2, 3\pi/4, \pi\)

# from ? import ?
# YOUR CODE HERE


# use list and range to create a list of indices called ivals
#ivals = ???(???(?))
# YOUR CODE HERE

# create a new list using list comprehension (see hint hidden below if you get stuck)

#pilist = [??? for ? in ???]
# YOUR CODE HERE

print(pilist)

Expected output
[-3.141592653589793, -2.356194490192345, -1.5707963267948966, -0.7853981633974483, 0.0, 0.7853981633974483, 1.5707963267948966, 2.356194490192345, 3.141592653589793]

Click for solution

  • This example shows why counting from zero can be useful (think about this).

Iterating using FOR loops

Another way to repeatedly perform some function for all the items in a list is using a for loop”.

Iterating through Strings

We can step through the letters in a string and repeat a block of INDENTED code for each character in the string.

Here the for statement takes each letter in the string "Hello World!" in turn and assigns it to the variable we call letr.

The indented code block is repeated in a loop for each character.

Each time the loop repeats the variable letr takes the next value.
Once the string is finished the indented block exits.

  • Notice that the code that is repeated has to be indented (usually by 4 spaces).

for letr in "Hello World!":
    print("The next character is: "+letr)
    print('...')

print("The end!")

This is equivalent to performing the following commands in sequence:

letr='H'
print("the next letter is:", letr)
letr='e'
print("the next letter is:", letr)

… and so on.

FOR Loops on Lists

We can step through the items in a list in the same way as stepping through the characters in a string.

for next_item in ["spam", "eggs", "cheese"]:
    print("buy some", next_item)

Performing calculations using FOR loops

Look at the three ways of performing the same task below to see how FOR loops can be used to simplify a task (the comma stops printing on a new line):

print(1**2, end=', ')
print(2**2, end=', ')
print(3**2)
  • The end=', ' argument makes sure each print() is followed by a comma and not a new line (return character).

We can rewrite this using a counter n:

n = 1
print(n**2, end=', ')
n = 2
print(n**2, end=', ')
n = 3
print(n**2)

This can now be made compact using a loop:

numbers = [1, 2, 3]
for n in numbers:
    print(n**2, end=', ')

Using the range() Function

The range function can be used directly to iterate a block of instructions for a set number of steps, i.e. for a certain range of values.

Here i is just a variable name for a counter that takes values in the range 0:5 (i.e. 6 values),
it could be used in the calculation, or as an index to access a value in a list or string, or not used at all:

  • Notice that a BLOCK of code is indented by the same number of spaces, and all of this repeats

n = 1

# for each iteration of the loop, times the previous n by 10
for i in range(6):
    n = n*10
    print(i, n)
    
print("""\nAnything after the indented block, using normal indentation (back to the margin), 
only gets executed AFTER the loop has finished iterating.""")

The full syntax for the range function is range(int1,int2,step), where the list generated is all the integers between the integers int1 and int2 in steps of step.
This does not include the actual value int2 (refer to Slicing rules).
If the start value (int1) is omitted the list starts from 0, and the default step is 1.

Exercise 2

Fibonacci Sequence Iteration

Numbers in the Fibonacci sequence are given by adding the previous two numbers \(x_N = x_{N-1} + x_{N-2}\) in the sequence: \(1, 1, 2, 3, 5, 8, \dots\)

Use the following outline for a code (so-called “pseudo-code”) to write a program to generate the first 12 values of the Fibonacci sequence.

  1. Define a new list containing first two values (1 and 1) using square bracket notation,

  2. use a FOR loop and the range() function to iterate for ten steps (12 values in total).

  3. use negative indexing to sum the last two values of the list and assign it to a variable for the next value, e.g.:

a_list = ["one", "two", "three", "four", "five"]
print("The last two values are", a_list[-2], "and", a_list[-1])

Results in:

The last two values are four and five
  1. append this next value (\(x_N = x_{N-1} + x_{N-2}\)) in the sequence to the end of the list using syntax like:

LISTNAME=LISTNAME+[NEWENTRY]

or the append function, which does the same thing:

LISTNAME.append(NEWENTRY)
  1. Print the list

# Fibonacci sequence

# first define the first two values
fib = [1, 1]

# iterate for 10 steps
#for ? in ???:
# YOUR CODE HERE
    # calculate next value as sum of previous two values of the list
    # join it to the end of the list
    # YOUR CODE HERE

# Print the list
# YOUR CODE HERE

Result: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

Click here for solution

  • Try printing the ratio of the last two steps at each iteration and see how they converge to the Golden Ratio.

Nested Loops

We can also have loops inside loops, which are called nested loops. In the case of a nested FOR loop, for each item in the list we then iterate over the contents and perform some action.

for a_word in ["spam", "eggs", "cheese"]:
    # print the word as a whole
    print(a_word+":", end=" ")

    # this inner loop loops over each word in the top-level outer loop
    for a_letter in a_word:
        print(a_letter, ";", end=" ")

    # Start a new line:
    print()

Where the general format is:

HEADER 1: 
    CODE FOR BLOCK 1
        HEADER 2: 
            CODE FOR BLOCK 2 
            ... 
        MORE CODE FOR BLOCK 1 
        ...
MAIN PROGRAMME
n = 3
for i in range(n):
    for j in range(1, 4):
        print(i, j, i*n + j)

Another useful function is enumerate(), which gives an index number to items in a list or string:

greeting = "Hello World!"
for idx, letr in enumerate(greeting):
    print(f"Character with index {idx} is: {letr}")
  • Note that the f"Some String with {variable}" allows the inline formatting of a variable directly in a string.

Task 3 (2%)

Chaos Generator!

Background:

The simple iterative formula: \(x_{i+1} = c - x_{i}^2\) can produce surprisingly complex sequences for different values of the constant \(c\).

For \(0<c\leq 0.75\), starting at some initial value (e.g.: \(x_0=0.5\)) the sequence settles, after a number of iterations, to a different single value of \(x\) for each value of \(c\).
E.g.: at \(c=0.75\) the sequence converges to: \(x_0 = 0.5\); \(x_1 = 0.75 - x_0^2 = 0.75 - 0.25 = 0.5\); \(x_3 = 0.5\) \(\cdots\)
E.g. at \(c=0.5\) the following happens: From \(c > 0.75\) until \(c\approx 1.25\) the sequence alternates between two values after settling down.
E.g. at \(c=1\) the following happens: At around \(c\approx 1.25\) it starts alternating between four values.
The number of values it alternates between keeps doubling at closer and closer \(c\) values until at a critical \(c=1.5\) the sequence reaches infinite period and never repeats (within the precision of the computer memory). E.g. at \(c=1.6\) the following happens: This is an example of “deterministic chaos”, which is the mathematical phenomenon behind the unpredictability of the weather (the so-called “butterfly effect”) see here for more information.

Your Task:

  • Define a function called quadmap that takes one argument c and performs the quadratic map iteration outlined above.

  • the structure of your code should be as follows:

def FUNCTION(ARGUMENTS):
    # initialise an empty list for storing values of x
    xvals = ???
    
    # set x to 0.5 for an initial value
    
    #use a for loop to iterate x = c - x**2 for 10,000 iterations until the values settle
    for ? in ???:
        #FORMULA
        
    #use another for loop to iterate for a final 16 steps and append the values to your list
    for ???
    
    return xvals
  • test your function by printing the results of several choices of c

  • Download as a .py python file / check in Spyder or otherwise before submitting exercise5.py to moodle.

# function definition
def quadmap(c): 
    # YOUR CODE HERE
    return xvals

#test your function on c=1.3
quadmap(1.3)
# code testing block (only works from the original notebook running on Jupyterhub)

import sys
sys.path.append('.checks/')
import check05
try: check05.test(quadmap)
except NameError as e: print("The function was not found, is it named correctly and the cell above run?\n--> "+str(e))

Expected Result:

[1.1486645691118087,
 -0.019430292332817123,
 1.2996224637398612,
 -0.3890185482572668,
 1.1486645691118087,
 -0.019430292332817123,
 1.2996224637398612,
 -0.3890185482572668,
 1.1486645691118087,
 -0.019430292332817123,
 1.2996224637398612,
 -0.3890185482572668,
 1.1486645691118087,
 -0.019430292332817123,
 1.2996224637398612,
 -0.3890185482572668]

Overall Behaviour:

The structure of the behaviour of the equation is both chaotic and also a fractal (more info on fractals).

  • Play with the interactive figure below and test out some values of c on your own function.

  • Try out very slightly different starting values for x to see the sensitivity to initial conditions (the butterfly effect) for a chaotic value of c.

from IPython.display import IFrame
IFrame('Figures/quadmap.html', width=900, height=500)

Extra Creative Challenge: Sorting Lists

The following code is a first attempt at sorting a list alphabetically.
Use your ingenuity to perfect the programme.
It might help to look on the internet for sorting algorithms and try to implement them in Python.
Or just use your own ideas.

shopping = ["potatoes", "pears", "coffee", "grapes", "peas", "bananas",
            "bleach", "toothpaste", "apples", "pesto", "pasta", "wine", "cabbage"]

L = len(shopping)

for i in range(L):
    for j in range(i, L):
        if shopping[i][0] > shopping[j][0]:
            tmp = shopping[i]
            shopping[i] = shopping[j]
            shopping[j] = tmp

print(shopping)

The best attempt I could think up in an evening is in the link below.

  • Does it work in all cases?

  • Is it the most efficient?

Link to one possible improvement