Monday, February 2nd, 2026¶

Last week, we looked at assigning and working with variables in Python. We also looked at printing and formatting strings.

Working with lists in Python¶

Another datatype in Python is the list type. Lists contain ordered collections of objects. To define a list, we surround a comma-separated collection with square brackets.

In [3]:
my_list = [10, 9, 8, 7, 3.1, -7.2, 'hello']

my_list
Out[3]:
[10, 9, 8, 7, 3.1, -7.2, 'hello']
In [4]:
another_list = ['a', 'b', 'c', 'd', 'e']
another_list
Out[4]:
['a', 'b', 'c', 'd', 'e']

To access elements of a list, we use square brackets again along with an index. Python is a 0-based indexing language, which means the index of each list starts at 0. That is, 0 indicates the first item in the list.

In [6]:
my_list[0]
Out[6]:
10
In [7]:
my_list[1]
Out[7]:
9
In [8]:
another_list[3]
Out[8]:
'd'

We can also access elements of a list by counting backward from the end using negative indices.

  • The -1st index gives the last element.
  • The -2nd index gives the second to last element.
In [9]:
my_list[-1]
Out[9]:
'hello'
In [10]:
my_list[-2]
Out[10]:
-7.2
In [11]:
another_list[-3]
Out[11]:
'c'

Note: We will get an error if we access indices beyond the length of the list:

In [15]:
my_list[7]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[15], line 1
----> 1 my_list[7]

IndexError: list index out of range
In [26]:
another_list[-6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[26], line 1
----> 1 another_list[-6]

IndexError: list index out of range

List operations¶

What sorts of operations can we perform on lists? For arithmetic operations, lists work very similarly to strings.

Addition of lists:

In [27]:
my_list + another_list
Out[27]:
[10, 9, 8, 7, 3.1, -7.2, 'hello', 'a', 'b', 'c', 'd', 'e']
In [28]:
another_list + my_list
Out[28]:
['a', 'b', 'c', 'd', 'e', 10, 9, 8, 7, 3.1, -7.2, 'hello']

Adding lists together concatenates them.

Multiplying a list and an integer:

In [29]:
another_list * 3
Out[29]:
['a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'e']

Multiplying a list by a integer concatenates the list multiple times.

Lists and strings share many properties. We can convert a string to a list using the list function:

In [30]:
my_string = 'Welcome to MTH 337!'
my_string_as_list = list(my_string)

print(my_string_as_list)
['W', 'e', 'l', 'c', 'o', 'm', 'e', ' ', 't', 'o', ' ', 'M', 'T', 'H', ' ', '3', '3', '7', '!']

We can find the length of a list (or string) using the len function.

In [34]:
len(my_list)
Out[34]:
7
In [35]:
len(another_list)
Out[35]:
5
In [36]:
len(my_string)
Out[36]:
19
In [37]:
len(my_string_as_list)
Out[37]:
19

What if we want to convert a list of string characters to a string? The str function can be used to convert objects to strings.

In [41]:
str(my_list)
Out[41]:
"[10, 9, 8, 7, 3.1, -7.2, 'hello']"
In [40]:
str(my_string_as_list)
Out[40]:
"['W', 'e', 'l', 'c', 'o', 'm', 'e', ' ', 't', 'o', ' ', 'M', 'T', 'H', ' ', '3', '3', '7', '!']"

Can we freely convert the string '12345' to a list and then back to a string?

In [42]:
my_string = '12345'
my_string_as_list = list(my_string)

print(my_string_as_list)
['1', '2', '3', '4', '5']
In [43]:
new_string = str(my_string_as_list)
print(new_string)
['1', '2', '3', '4', '5']
In [52]:
my_string[0]
Out[52]:
'1'
In [53]:
my_string_as_list[0]
Out[53]:
'1'
In [54]:
new_string[0]
Out[54]:
'['

It does not look like this is doing quite what we want, since the resulting includes the list delimitors (brackets) and element separators (commas).

We can fix this by using the .join method on a string. The .join method takes in a list of strings and concetanates them. However, it uses the object from which it is called to separate each concatenation.

For example, calling 'abc'.join(['hello','goodbye','zzz']) will produce a string that concatenates the strings 'hello', 'goodbye', and 'zzz' separated by the string 'abc'. That is, it produces the string 'helloabcgoodbyeabczzz'.

In particular, if we call the .join method from an empty string '', we will get simple concatenation.

In [50]:
print(my_string)
print(my_string_as_list)
print(''.join(my_string_as_list))
print(' - '.join(my_string_as_list))
12345
['1', '2', '3', '4', '5']
12345
1 - 2 - 3 - 4 - 5

Exercise: Use the .join method and string formatting to take in an integer n and it's prime factorization (as a list of strings) factors and print out a sentence stating the prime factorization.

As an example, if n=22 and factors=['2', '11'], we could print something like '22 = 2 * 11'.

In [2]:
factors = ['2', '3', '7', '11']
n = 2*3*7*11

print(n, factors)
462 ['2', '3', '7', '11']
In [4]:
factorization = ' * '.join(factors)

'{} = {}'.format(n, factorization)
Out[4]:
'462 = 2 * 3 * 7 * 11'

Working with loops in Python¶

It often happens that we want to perform the same (or similar) operations many times. We can perform iterative operations using a for loop. For example, we can iterate through the items in a list and perform some desired operations.

The syntax for writing a for loop is: for (some variable name) in (some iterable object): (do something)

The variable (some variable name) will sequentially take on each of the values stored in (some iterable object) (a list, for example), and for each value will (do something).

In [7]:
my_list = ['a', 'b', 3, 4, 5]

for element in my_list:
    print(5*element)
aaaaa
bbbbb
15
20
25

Key info: The spacing in Python is critical!!!. In particular, the spacing decides what operations are part of a for loop and what operations are not.

In [9]:
my_list = ['a', 'b', 3, 4, 5]

for element in my_list:
    print(5*element)
    print('Hello!')
aaaaa
Hello!
bbbbb
Hello!
15
Hello!
20
Hello!
25
Hello!
In [10]:
my_list = ['a', 'b', 3, 4, 5]

for element in my_list:
    print(5*element)
print('Hello!')
aaaaa
bbbbb
15
20
25
Hello!

We can also use for loops inside other for loops. We call these "nested loops".

Exercise: Write nested for loops that iterate through all combinations of integers from the two lists [1,2,3] and [4,5,6] and print out the sum for each combination.

In [17]:
first_list = [1,2,3]
second_list = [4,5,6]

for first_element in first_list:
    for second_element in second_list:
        print('{} + {} = {}'.format(first_element, second_element, first_element + second_element))
1 + 4 = 5
1 + 5 = 6
1 + 6 = 7
2 + 4 = 6
2 + 5 = 7
2 + 6 = 8
3 + 4 = 7
3 + 5 = 8
3 + 6 = 9

What if we wanted to iterate through pairs from each list? That is, suppose we want to consider the lists in parallel and iterate through the three pairs (1,4), (2,5), and (3,6).

Exercise: Write a for loop that iterates through the two lists [1,2,3] and [4,5,6] in parallel and prints out the sum of each corresponding pair.

In [18]:
first_names = ['Seamus', 'Leighton', 'Gareth']
last_names = ['Coleman', 'Baines', 'Barry']

for first_name in first_names:
    for last_name in last_names:
        print(first_name, last_name)
Seamus Coleman
Seamus Baines
Seamus Barry
Leighton Coleman
Leighton Baines
Leighton Barry
Gareth Coleman
Gareth Baines
Gareth Barry
In [19]:
first_names = ['Seamus', 'Leighton', 'Gareth']
last_names = ['Coleman', 'Baines', 'Barry']

indices = [0,1,2]

for i in indices:
    first_name = first_names[i]
    last_name = last_names[i]
    print(first_name, last_name)
Seamus Coleman
Leighton Baines
Gareth Barry

Later on, we'll see how to use the zip function to achieve this goal in a more natual (and extendable) way.

The range function¶

We can use other types of iterables to setup for loops. In the examples above, we've been iterating through a pre-defined list. Suppose we want to perform some operation on the first 10,000 positive integers.

In [ ]:
integers = [1,2,3,4,5,6,7,8,9,

Of course, it's not reasonable for us to write down a list of the first 10,000 positive integers in order to iterate through them. Instead, we can use the range function.

Note: We can use the help function to learn more about something in Python. For example, help(range) will tell us about the range function.

In [20]:
help(range)
Help on class range in module builtins:

class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
 |
 |  Methods defined here:
 |
 |  __bool__(self, /)
 |      True if self else False
 |
 |  __contains__(self, key, /)
 |      Return bool(key in self).
 |
 |  __eq__(self, value, /)
 |      Return self==value.
 |
 |  __ge__(self, value, /)
 |      Return self>=value.
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __getitem__(self, key, /)
 |      Return self[key].
 |
 |  __gt__(self, value, /)
 |      Return self>value.
 |
 |  __hash__(self, /)
 |      Return hash(self).
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  __le__(self, value, /)
 |      Return self<=value.
 |
 |  __len__(self, /)
 |      Return len(self).
 |
 |  __lt__(self, value, /)
 |      Return self<value.
 |
 |  __ne__(self, value, /)
 |      Return self!=value.
 |
 |  __reduce__(self, /)
 |      Helper for pickle.
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __reversed__(self, /)
 |      Return a reverse iterator.
 |
 |  count(self, object, /)
 |      rangeobject.count(value) -> integer -- return number of occurrences of value
 |
 |  index(self, object, /)
 |      rangeobject.index(value) -> integer -- return index of value.
 |      Raise ValueError if the value is not present.
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(*args, **kwargs)
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  start
 |
 |  step
 |
 |  stop

In particular:

  • range(n) will give a sequence of integers starting at 0 and going up to n-1.
  • range(m,n) will give a sequence of integers starting at m and going up to n-1.
  • range(m,n,k) will give a sequence of integers starting m, stepping by k, and stopping before n.
In [21]:
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9
In [22]:
for i in range(34, 41):
    print(i)
34
35
36
37
38
39
40
In [25]:
for i in range(1, 22, 3):
    print(i)
1
4
7
10
13
16
19
In [27]:
for i in range(10, 1, -1):
    print(i)
10
9
8
7
6
5
4
3
2
In [29]:
range(10)
Out[29]:
range(0, 10)
In [30]:
list(range(10))
Out[30]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Note: the range doesn't exactly generate a list. Instead, it is what's called a generator.

Exercise: Write Python code to print the cubes of the first $50$ positive integers.

In [36]:
for n in range(1,51):
    print(n**3)
1
8
27
64
125
216
343
512
729
1000
1331
1728
2197
2744
3375
4096
4913
5832
6859
8000
9261
10648
12167
13824
15625
17576
19683
21952
24389
27000
29791
32768
35937
39304
42875
46656
50653
54872
59319
64000
68921
74088
79507
85184
91125
97336
103823
110592
117649
125000