Monday, August 31st, 2026¶
Last week, we started working with different Python datatypes, including:
- Integers (
int) - Floats (
float) - Strings (
str) - Lists (
list)
We had just started looking at how we can iterate through a list to perform some actions on each of its elements.
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).
for num in [1,2,3,4,5]:
print(num)
1 2 3 4 5
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.
for num in [1,2,3,4,5]:
print(num**2)
print('Done')
1 4 9 16 25 Done
In the cell above, notice how the print('Done') action only occurs after the loop has completed, because it is not indented to be part of the for loop.
for num in [1,2,3,4,5]:
print(num**2)
print('Done')
1 Done 4 Done 9 Done 16 Done 25 Done
This time in the cell above, the print('Done') action occurs during every iteration of the loop, because it is indented to be part of the for loop.
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.
for n in [1,2,3]:
for m in [4,5,6]:
print('{} + {} = {}'.format(n,m,n+m))
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: Define two lists: one that contains a list of first names and another containing the corresponding last names (in the same order).
Write a for loop that iterates through the two lists in parallel and prints out the sum of each corresponding pair.
my_list1 = [1,2,3,4,5,6]
my_list2 = [4,5,6,7,8,9]
for i in [0,1,2]:
n = my_list1[i]
m = my_list2[i]
print('{} + {} = {}'.format(n,m,n+m))
1 + 4 = 5 2 + 5 = 7 3 + 6 = 9
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.
for num in [1,2,3,4,5,6,7,8,
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.
Let's test out these different uses with for loops.
for num in range(10):
print(num)
0 1 2 3 4 5 6 7 8 9
for num in range(4,10):
print(num)
4 5 6 7 8 9
for num in range(2, 10, 3):
print(num)
2 5 8
my_list1 = [1,2,3,4,5,6]
my_list2 = [4,5,6,7,8,9]
for i in range(len(my_list1)):
n = my_list1[i]
m = my_list2[i]
print('{} + {} = {}'.format(n,m,n+m))
1 + 4 = 5 2 + 5 = 7 3 + 6 = 9 4 + 7 = 11 5 + 8 = 13 6 + 9 = 15
range(10)
range(0, 10)
Note: the range doesn't exactly generate a list. Instead, it is what's called a generator (i.e. instructions on how to construct a sequence of values).
Exercise: Write Python code to print the cubes of the first $50$ positive integers.
for num in range(1,51):
print(num**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
Constructing lists¶
So far, we've explicitly generated lists using square brackets and comma-separated inputs (which we've had to manually type in). Suppose we want to generate a list containing the cubes of the first 50 positive integers. Our current strategy is not reasonable for this sort of task.
The .append method (attached to a list) can be used to add an element to a list. That is, we can write something like <some list>.append(<some new element>) to add <some new element> to <some list>.
my_list = [1,2,3]
print(my_list)
[1, 2, 3]
my_list.append('four')
print(my_list)
[1, 2, 3, 'four']
To build a list of the cubes of the first $50$ positive integers, we can start with an empty list [] and then iteratively use the .append method to add elements to that list.
cubes = []
for num in range(1,51):
cubes.append(num**3)
print(cubes)
[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]
Exercise: Generate a list of the squares of the first $40$ positive integers. Then print the remainder of each after division by $7$.
squares = []
for num in range(1,41):
squares.append(num**2)
for square in squares:
print(square % 7)
1 4 2 2 4 1 0 1 4 2 2 4 1 0 1 4 2 2 4 1 0 1 4 2 2 4 1 0 1 4 2 2 4 1 0 1 4 2 2 4
Boolean expressions¶
There are two Boolean values, namely True and False.
We can write statements that evalute to either True or False called Boolean expressions. For example, we can compare two numbers using < or > to see if one is less than the other or one is greater than the other.
4 < 5
True
3.2 > 3.1
True
7 < 4
False
Similarly, we can use <= or >= for less than/greater than or equal to.
3.2 <= 3.2
True
7 >= 6.9
True
Inequality checks can also be chained together.
3 < 5 < 6
True
3 < 6 < 5
False
We can use a double equality == to check whether two objects are equal to one another.
5 == 6
False
5 == 5.0
True
We can also check whether two lists are equal to one another (i.e. if they contain equal objects in the same order).
my_list1 = [1,2,3]
my_list2 = [1,2,4]
my_list3 = [1,2,3]
my_list1 == my_list2
False
my_list1 == my_list3
True
We can construct more complicated Boolean expressions using the and, or, and not operators. That is:
(some expression) and (some other expression)will evaluate asTrueif(some expression)and(some other expression)are bothTrue.(some expression) or (some other expression)will evaluate asTrueif either(some expression)or(some other expression)areTrue(or both).not (some expression)will evaluate asTrueif(some expression)isFalse.
(3 < 5) and (3.0 < 5.0)
True
(6 < 7) or (7 < 6)
True
(6 < 6) or (6 < 6)
False
not (5 > 1)
False
In-class Exercise: For each number $n=1,2,3,...,20$, print out True if it is both even and a multiple of $3$, and False otherwise.
Note: A number $n$ is even if it has remainder $0$ after division by $2$. Similarly, $n$ is a multiple of $3$ if it has remainder $0$ after division by $3$.
for num in range(1,21):
print(num, (num % 2 == 0) and (num % 3 == 0))
1 False 2 False 3 False 4 False 5 False 6 True 7 False 8 False 9 False 10 False 11 False 12 True 13 False 14 False 15 False 16 False 17 False 18 True 19 False 20 False
Using if statements¶
We can use an if statement to perform some operations only when a Boolean expression is True. The syntax for writing an if statement is:
if (some Boolean expression):
(do something)
Again, spacing is CRITICAL, as it indicates which operations are part of the if statement (which will only run when the Boolean expression is True), and which operations are outside of the if statement (which will run regardless).
num = 7
if (num % 2 == 0):
print(num, 'is even')
if (num % 2 == 1):
print(num, 'is odd')
print('Done')
7 is odd Done
num = 7
if (num % 3 == 0):
print(num, 'is a multiple of 3')
if (num % 3 == 1):
print(num, 'is one more than a multiple of 3')
if (num % 3 == 2):
print(num, 'is two more than a multiple of 3')
print('Done')
7 is one more than a multiple of 3 Done
Optionally, we can include an else block immediately following an if block. In this case, the code inside the else block will only occur if the Boolean expression in the if statement was False.
num = 7
if (num % 2 == 0):
print(num, 'is even')
else:
print(num, 'is odd')
print('Done')
7 is odd Done
Exercise: For each number $n=1,2,3,...,20$, print out a statement indicating whether the number is even and a multiple of $3$ or not.
for num in range(1,21):
if (num % 2 == 0) and (num % 3 == 0):
print(num, 'is both even and a multiple of 3')
else:
print(num, 'is not both even and a multiple of 3')
1 is not both even and a multiple of 3 2 is not both even and a multiple of 3 3 is not both even and a multiple of 3 4 is not both even and a multiple of 3 5 is not both even and a multiple of 3 6 is both even and a multiple of 3 7 is not both even and a multiple of 3 8 is not both even and a multiple of 3 9 is not both even and a multiple of 3 10 is not both even and a multiple of 3 11 is not both even and a multiple of 3 12 is both even and a multiple of 3 13 is not both even and a multiple of 3 14 is not both even and a multiple of 3 15 is not both even and a multiple of 3 16 is not both even and a multiple of 3 17 is not both even and a multiple of 3 18 is both even and a multiple of 3 19 is not both even and a multiple of 3 20 is not both even and a multiple of 3
Exercise: Build a list of all numbers less than $100$ that are both even and a multiple of $3$.
even_multiples_of_3 = []
for num in range(100):
if (num % 2 == 0) and (num % 3 == 0):
even_multiples_of_3.append(num)
print(even_multiples_of_3)
[0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]
We very often want to perform different operations based on several Boolean expressions. We can supplement an if statement with an elif statement (which is short for "else if") with a new Boolean expression to perform operations only in the case that the first if expression was False and the new expression is True.
num = 101
if num > 10000:
print(num,'is very large')
elif num > 1000:
print(num,'is large')
elif num > 100:
print(num,'is medium')
elif num > 10:
print(num,'is medium-small')
else:
print(num,'is small')
101 is medium
Exercise: Use an if/elif/else triple to print a string stating whether an integer n is a multiple of $3$, one more than a multiple of $3$, or two more than a multiple of $3$.
num = 7
if (num % 3 == 0):
print(num, 'is a multiple of 3')
elif (num % 3 == 1):
print(num, 'is one more than a multiple of 3')
else:
print(num, 'is two more than a multiple of 3')
print('Done')
7 is one more than a multiple of 3 Done
Exercise: Out of the squares of the first $40$ positive integers, count how many have remainder $1$ after division by $7$.
squares = []
for num in range(1,41):
squares.append(num**2)
squares_with_remainder_1 = []
for square in squares:
if (square % 7 == 1):
squares_with_remainder_1.append(square)
print(len(squares_with_remainder_1))
11
squares_with_remainder_1
[1, 36, 64, 169, 225, 400, 484, 729, 841, 1156, 1296]
Some thoughts:
It would be nice if we defined some parameters to control the above cells. That is, it would be nice if we could easily change, say, the divisor that we are using.
div = 13
squares = []
for num in range(1,41):
squares.append(num**2)
squares_with_remainder_1 = []
for square in squares:
if (square % div == 1):
squares_with_remainder_1.append(square)
num_squares_with_remainder_1 = len(squares_with_remainder_1)
print(num_squares_with_remainder_1,'squares have remainder 1 after division by',div)
Exercise: Count how many of the first $1000$ cubes have remainder $0$ after division by $4$. Use variables (your choice of variable names) to define the parameters $1000$, $0$, and $4$. Your code should print a statement like "W of the first X cubes have remainder Y after division by Z."
N = 1000
div = 4
rem = 0
exp = 3
powers = []
for num in range(1,N+1):
powers.append(num**exp)
powers_with_rem = []
for power in powers:
if (power % div == rem):
powers_with_rem.append(power)
num_powers_with_rem = len(powers_with_rem)
output_str = '{} of the first {} {}th powers have remainder {} after divison by {}.'
print(output_str.format(num_powers_with_rem, N, exp, rem, div))
500 of the first 1000 3th powers have remainder 0 after divison by 4.
Exercise: Use the code above inside a for loop to count how many of the first $1000$ cubes have remainder $0$, then remainder $1$, then remainder $2$, then remainder $3$ after division by $4$.