5. Loops and List Comprehensions
# Loops
Loops are a way to repeatedly execute some code. Here's an example:
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
for planet in planets:
print(planet, end=' ') # print all on same line
2
3
The for
loop specifies
- the variable name to use (in this case,
planet
) - the set of values to loop over (in this case,
planets
)
You use the word "in
" to link them together.
The object to the right of the "in
" can be any object that supports iteration. Basically, if it can be thought of as a group of things, you can probably loop over it. In addition to lists, we can iterate over the elements of a tuple:
multiplicands = (2, 2, 2, 3, 3, 5)
product = 1
for mult in multiplicands:
product = product * mult
product
2
3
4
5
You can even loop through each character in a string:
s = 'steganograpHy is the practicE of conceaLing a file, message, image, or video within another fiLe, message, image, Or video.'
msg = ''
# print all the uppercase letters in s, one at a time
for char in s:
if char.isupper():
print(char, end='')
2
3
4
5
6
# range()(左闭右开)
range()
is a function that returns a sequence of numbers. It turns out to be very useful for writing loops.
For example, if we want to repeat some action 5 times:
for i in range(5):
print("Doing important work. i =", i)
2
# while
loops
The other type of loop in Python is a while
loop, which iterates until some condition is met:
i = 0
while i < 10:
print(i, end=' ')
i += 1
2
3
4
The argument of the while
loop is evaluated as a boolean statement, and the loop is executed until the statement evaluates to False.
# List comprehensions
List comprehensions are one of Python's most beloved and unique features. The easiest way to understand them is probably to just look at a few examples:
squares = [n**2 for n in range(10)]
squares
2
Here's how we would do the same thing without a list comprehension:
squares = []
for n in range(10):
squares.append(n**2)
squares
2
3
4
We can also add an if
condition:
short_planets = [planet for planet in planets if len(planet) < 6]
short_planets
2
(If you're familiar with SQL, you might think of this as being like a "WHERE" clause)
Here's an example of filtering with an if
condition and applying some transformation to the loop variable:
# str.upper() returns an all-caps version of a string
loud_short_planets = [planet.upper() + '!' for planet in planets if len(planet) < 6]
loud_short_planets
2
3
People usually write these on a single line, but you might find the structure clearer when it's split up over 3 lines:
[
planet.upper() + '!'
for planet in planets
if len(planet) < 6
]
2
3
4
5
(Continuing the SQL analogy, you could think of these three lines as SELECT, FROM, and WHERE)
The expression on the left doesn't technically have to involve the loop variable (though it'd be pretty unusual for it not to). What do you think the expression below will evaluate to? Press the 'output' button to check.
[32, 32, 32, 32, 32, 32, 32, 32]
List comprehensions combined with functions like min
, max
, and sum
can lead to impressive one-line solutions for problems that would otherwise require several lines of code.
For example, compare the following two cells of code that do the same thing.
def count_negatives(nums):
"""Return the number of negative numbers in the given list.
>>> count_negatives([5, -1, -2, 0, 3])
2
"""
n_negative = 0
for num in nums:
if num < 0:
n_negative = n_negative + 1
return n_negative
2
3
4
5
6
7
8
9
10
11
Here's a solution using a list comprehension:
def count_negatives(nums):
return len([num for num in nums if num < 0])
2
Much better, right?
Well if all we care about is minimizing the length of our code, this third solution is better still!
def count_negatives(nums):
# Reminder: in the "booleans and conditionals" exercises, we learned about a quirk of
# Python where it calculates something like True + True + False + True to be equal to 3.
return sum([num < 0 for num in nums])
2
3
4
Which of these solutions is the "best" is entirely subjective. Solving a problem with less code is always nice, but it's worth keeping in mind the following lines from The Zen of Python (opens new window):
Readability counts. 可读性 Explicit is better than implicit.
So, use these tools to make compact readable programs. But when you have to choose, favor code that is easy for others to understand.
# Exercise: Loops and List Comprehensions
You know the deal at this point. We have some fun coding challenges (opens new window) for you.
# 1.
Have you ever felt debugging involved a bit of luck? The following program has a bug. Try to identify the bug and fix it.
def has_lucky_number(nums):
"""Return whether the given list of numbers is lucky. A lucky list contains
at least one number divisible by 7.
"""
for num in nums:
if num % 7 == 0:
return True
# We've exhausted the list without finding a lucky number
return False
q1.check()
2
3
4
5
6
7
8
9
10
11
Solution: Remember that return
causes a function to exit immediately. So our original implementation always ran for just one iteration. We can only return False
if we've looked at every element of the list (and confirmed that none of them are lucky). Though we can return early if the answer is True
:
def has_lucky_number(nums):
for num in nums:
if num % 7 == 0:
return True
# We've exhausted the list without finding a lucky number
return False
2
3
4
5
6
Here's a one-line version using a list comprehension with Python's any
function (you can read about what it does by calling help(any)
):
def has_lucky_number(nums):
return any([num % 7 == 0 for num in nums])
2
# 2.
# a.
Look at the Python expression below. What do you think we'll get when we run it? When you've made your prediction, uncomment the code and run the cell to see if you were right.
[1, 2, 3, 4] > 2 # '>' not supported between instances of 'list' and 'int'
# b
R and Python have some libraries (like numpy and pandas) compare each element of the list to 2 (i.e. do an 'element-wise' comparison) and give us a list of booleans like [False, False, True, True]
.
Implement a function that reproduces this behaviour, returning a list of booleans corresponding to whether the corresponding element is greater than n.
def elementwise_greater_than(L, thresh):
"""Return a list with the same length as L, where the value at index i is
True if L[i] is greater than thresh, and False otherwise.
>>> elementwise_greater_than([1, 2, 3, 4], 2)
[False, False, True, True]
"""
return [num > thresh for num in L]
q2.check()
2
3
4
5
6
7
8
9
10
Solution: Here's one solution:
def elementwise_greater_than(L, thresh):
res = []
for ele in L:
res.append(ele > thresh)
return res
2
3
4
5
And here's the list comprehension version:
def elementwise_greater_than(L, thresh):
return [ele > thresh for ele in L]
2
# 3.
Complete the body of the function below according to its docstring
def menu_is_boring(meals):
"""Given a list of meals served over some period of time, return True if the
same meal has ever been served two days in a row, and False otherwise.
"""
# Iterate over all indices of the list, except the last one
for i in range(len(meals)-1):
if meals[i] == meals[i+1]:
return True
return False
q3.check()
2
3
4
5
6
7
8
9
10
11
# 4. 🌶️
Next to the Blackjack table, the Python Challenge Casino has a slot machine. You can get a result from the slot machine by calling play_slot_machine()
. The number it returns is your winnings in dollars. Usually it returns 0. But sometimes you'll get lucky and get a big payday. Try running it below:
play_slot_machine()
By the way, did we mention that each play costs $1? Don't worry, we'll send you the bill later.
On average, how much money can you expect to gain (or lose) every time you play the machine? The casino keeps it a secret, but you can estimate the average value of each pull using a technique called the Monte Carlo method. To estimate the average outcome, we simulate the scenario many times, and return the average result.
Complete the following function to calculate the average value per play of the slot machine.
def estimate_average_slot_payout(n_runs):
"""Run the slot machine n_runs times and return the average net profit per run.
Example calls (note that return value is nondeterministic!):
>>> estimate_average_slot_payout(1)
-1
>>> estimate_average_slot_payout(1)
0.5
"""
sum = 0
for i in range(n_runs):
sum = sum + play_slot_machine() - 1 #每局需要支付1美元
return res / n_runs
estimate_average_slot_payout(10000000)
2
3
4
5
6
7
8
9
10
11
12
13
14
Solution: The exact expected value of one pull of the slot machine is 0.025 - i.e. a little more than 2 cents. See? Not every game in the Python Challenge Casino is rigged against the player!
Because of the high variance of the outcome (there are some very rare high payout results that significantly affect the average) you might need to run your function with a very high value of n_runs
to get a stable answer close to the true expectation.
If your answer is way higher than 0.025, then maybe you forgot to account for the $1 cost per play?