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.
my_list = [10, 9, 8, 7, 3.1, -7.2, 'hello']
my_list
[10, 9, 8, 7, 3.1, -7.2, 'hello']
another_list = ['a', 'b', 'c', 'd', 'e']
another_list
['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.
my_list[0]
10
my_list[1]
9
another_list[3]
'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.
my_list[-1]
'hello'
my_list[-2]
-7.2
another_list[-3]
'c'
Note: We will get an error if we access indices beyond the length of the list:
my_list[7]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) Cell In[15], line 1 ----> 1 my_list[7] IndexError: list index out of range
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:
my_list + another_list
[10, 9, 8, 7, 3.1, -7.2, 'hello', 'a', 'b', 'c', 'd', 'e']
another_list + my_list
['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:
another_list * 3
['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:
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.
len(my_list)
7
len(another_list)
5
len(my_string)
19
len(my_string_as_list)
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.
str(my_list)
"[10, 9, 8, 7, 3.1, -7.2, 'hello']"
str(my_string_as_list)
"['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?
my_string = '12345'
my_string_as_list = list(my_string)
print(my_string_as_list)
['1', '2', '3', '4', '5']
new_string = str(my_string_as_list)
print(new_string)
['1', '2', '3', '4', '5']
my_string[0]
'1'
my_string_as_list[0]
'1'
new_string[0]
'['
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.
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'.
factors = ['2', '3', '7', '11']
n = 2*3*7*11
print(n, factors)
462 ['2', '3', '7', '11']
factorization = ' * '.join(factors)
'{} = {}'.format(n, factorization)
'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).
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.
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!
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.
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.
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
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.
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.
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 at0and going up ton-1.range(m,n)will give a sequence of integers starting atmand going up ton-1.range(m,n,k)will give a sequence of integers startingm, stepping byk, and stopping beforen.
for i in range(10):
print(i)
0 1 2 3 4 5 6 7 8 9
for i in range(34, 41):
print(i)
34 35 36 37 38 39 40
for i in range(1, 22, 3):
print(i)
1 4 7 10 13 16 19
for i in range(10, 1, -1):
print(i)
10 9 8 7 6 5 4 3 2
range(10)
range(0, 10)
list(range(10))
[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.
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