Most programs require code that runs repeatedly. Such code runs while a given condition remains truthy or until it becomes falsy (they mean the same thing). Most programming languages, including Python, use loops to provide this capability.
Python loops have several forms, but the main looping structures are the for
and while
statements. These loops execute a block repeatedly while a condition remains truthy. You can also think of the loop running until the condition becomes falsy. Both mental models are equivalent.
Python has three other looping mechanisms: comprehensions, generators, and functional loops. We'll talk about comprehensions in this chapter. However, we'll postpone the discussion of generators and functional loops to a later course in the Core Curriculum.
Let's begin with the while loop.
A while
loop uses the while
keyword followed by a conditional expression, a colon (:
), and a block. The loop repeatedly executes the block while the conditional expression remains truthy. In most programs, that loop should ultimately stop repeating. That means the block must do something to help Python know when to stop. That is, it must arrange to terminate the loop. That's usually triggered by evaluating a conditional expression. Otherwise, the loop is an infinite loop that never stops repeating.
To see why a while
loop is useful, consider writing some code that prints numbers from 1 to 10:
print(1)
print(2)
print(3)
print(4)
print(5)
print(6)
print(7)
print(8)
print(9)
print(10)
While that code is straightforward and readily understood, it's easy to see why this approach is unsustainable. Suppose we need to print the numbers from 1 to 1000 or 1,000,000. If you had to write all that code for such a simple task, you would soon regret your career choice.
Let's rewrite the program with a while
loop. Create a file named counter.py
with the following code and run it:
counter = 1
while counter <= 10:
print(counter)
counter += 1
This code does the same thing as the first program but more programmatically. It iterates over the block for all integer values between 1 and 10, inclusive. Each time the block runs is called an iteration.
If you want to print 1000 numbers or even a million, all you have to change is the conditional expression:
counter = 1
# highlight
while counter <= 1000:
# endhighlight
print(counter)
counter += 1
Go ahead and run the code now using the command line:
python counter.py
When Python encounters this while
loop, it evaluates the conditional expression, counter <= 1000
. Since counter
's initial value is 1
, the expression is initially True
, so Python executes the block. We print counter
's value inside the block, then increment it by 1
.
After the first iteration, Python re-evaluates the conditional expression. This time, counter
is 2
, which is still less than or equal to 1000
; thus, the block runs again. After 1000 iterations, counter
's value becomes 1001. Since the loop condition is no longer truthy, the program stops looping. It continues with the first expression or statement after the loop.
Line 4 in this example is crucial. The block must modify counter
somehow, ultimately making the loop condition falsy. If it doesn't, the loop never ends, which is usually not what you want. If your program never stops running, you probably have an infinite loop somewhere in the program. Try commenting out line 4 and rerun the code. You'll see that it continually prints the number 1
. You can use the Control+c
keystroke to terminate the program.
Go ahead and uncomment line 4.
One of the most common uses of loops in programming is to iterate over a sequence's elements and perform some action on each element. For example, we may want to iterate over a list of names and create a new list that contains the names in uppercase. Here's one way to do that:
names = ['Chris', 'Max', 'Karis', 'Victor']
upper_names = []
index = 0
while index < len(names):
upper_name = names[index].upper()
upper_names.append(upper_name)
index += 1
print(upper_names);
# ['CHRIS', 'MAX', 'KARIS', 'VICTOR']
A bit of explanation is in order here. The variable names
holds a list of names. We want to append each name, in uppercase, to the initially empty upper_names
list. Since list indexes are zero-based, we initialize an index
variable with 0
.
Next, we use a loop that executes as long as the number in index
is smaller than the length of the names
list. Line 8 increments the index by 1
after each iteration, which ensures that index < len(names)
becomes falsy after the loop handles the last element.
Line 6 accesses the name stored at names[index]
and uses it to call str.upper
. That method returns the name in uppercase, which we assign to upper_name
. It doesn't change the original name in the names
list.
Line 7 uses list.append
to append the latest uppercase name to the upper_names
list. Over the four iterations of the names
list, line 7 appends four uppercase names to upper_names
, one per iteration, in the same order the loop processes them.
Notice that we initialized names
, upper_names
, and index
before the loop. We don't want to initialize them inside the loop; they would get reset during every iteration. Basically, nothing would change. That wouldn't work well even if the code ran without error.
for
loops have the same purpose as while
loops, but they use a condensed syntax that works well when iterating over lists and other sequences. A for
loop lets you forget about indexing your sequences. You don't have to initialize or increment the index value or even need a condition. Moreover, for
loops work on all built-in collections (including strings). Most loops you write in Python will be for
loops.
for element in collection:
# loop body: do something with the element
If collection
is a sequence, this structure behaves in much the same way as:
index = 0
while index < len(collection):
# loop body
# do something with collection[index]
index += 1
The for
loop is more elegant and significantly less error-prone.
Let's rewrite names.py
with a for
loop to better illustrate using for
:
names = ['Chris', 'Max', 'Karis', 'Victor']
upper_names = []
# highlight
for name in names:
# endhighlight
upper_name = name.upper()
upper_names.append(upper_name)
# highlight
# Deleted: index += 1
# endhighlight
print(upper_names);
# ['CHRIS', 'MAX', 'KARIS', 'VICTOR']
The result is the same as with the while
loop. However, the code is much easier to read and maintain.
For illustrative purposes, let's see what happens when we use a for
loop to iterate over a string:
for char in 'Launch School':
print(char)
L
a
u
n
c
h
S
c
h
o
o
l
If you want to work with words instead of characters, you can use the split
method:
for word in 'Launch School'.split():
print(word)
Launch
School
You can also use for
loops with other collections, such as sets and dicts. Any iterable collection, in fact:
# Looping over a set
my_set = {1000, 2000, 3000, 4000, 5000}
for member in my_set:
print(member)
# The output may not be in this sequence since it
# comes from a set.
4000
2000
5000
3000
1000
# Looping over a dictionary
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in my_dict:
print(key)
a
b
c
Using a for
loop with a dict
iterates over the dict
keys by default. If you want the values or pairs, you can request them with the values
or items
methods:
# Looping over a dictionary's values
my_dict = {'a': 1, 'b': 2, 'c': 3}
for value in my_dict.values():
print(value)
1
2
3
# Looping over a dictionary's key/value pairs
my_dict = {'a': 1, 'b': 2, 'c': 3}
for item in my_dict.items():
print(item)
('a', 1)
('b', 2)
('c', 3)
A more Pythonic way to iterate over both the keys and values simultaneously is to use tuple unpacking:
# Looping over a dictionary's key/value pairs
my_dict = {'a': 1, 'b': 2, 'c': 3}
for (key, value) in my_dict.items():
print(f'{key} = {value}')
a = 1
b = 2
c = 3
We'll cover tuple unpacking in the Core Curriculum. For now, all you need to know is that each key returned by the items
method gets assigned to the key
variable, and each associated value gets assigned to the value
variable.
By the way: you don't need the parentheses around key, value
. The following code works and is more Pythonic:
# Looping over a dictionary's key/value pairs
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key, value in my_dict.items():
print(f'{key} = {value}')
You will often need to nest loops within one or more outer loops. For instance, suppose you want to create a deck of cards given two lists: the suits and ranks you want to combine:
suits = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
ranks = [
'2', '3', '4', '5', '6', '7', '8', '9', '10',
'Jack', 'Queen', 'King', 'Ace',
]
deck = []
for suit in suits:
for rank in ranks:
card = f'{rank} of {suit}'
deck.append(card)
print(deck)
With nested for
loops, you start with the outer loop and assign the variable to the first element of its collection (suit
and suits
). You then process the inner loop in its entirety. Here, we match the first suit with every possible card rank, appending each card to the deck.
Once the inner loop finishes processing, control returns to the outer loop. Working with the second element of the outer loop's collection, it again iterates over the inner loop's collection. This continues until all members of the outer loop's collection have been processed. In our card deck example, the outer loop runs 4 times, while the inner loop runs 4 * 13 (52) times.
Nesting 3 or more levels deep is possible but frequently inadvisable. Too much nesting is hard to understand. Using one or more helper functions to handle the more deeply nested processing is often a good idea.
You can also nest while
loops and mix for
and while
loops.
Python uses the keywords continue
and break
to provide more control over for
and while
loops. continue
starts a new loop iteration; break
terminates the loop early.
Let's stick with the names.py
program. Suppose we want all the uppercase names in our upper_names
list except 'Max'
. The continue
statement can help us do that.
names = ['Chris', 'Max', 'Karis', 'Victor']
upper_names = []
for name in names:
if name == 'Max':
continue
upper_name = name.upper()
upper_names.append(upper_name)
print(upper_names);
# ['CHRIS', 'KARIS', 'VICTOR']
The result doesn't contain 'MAX'
.
When a loop encounters the continue
keyword, it skips running the rest of the block and jumps ahead to the next iteration. In this example, we tell the loop to ignore 'Max'
and skip to the next iteration without adding 'MAX'
to upper_names
.
You can often rewrite a loop that uses continue
with a negated if
conditional:
names = ['Chris', 'Max', 'Karis', 'Victor']
upper_names = []
for name in names:
if name != 'Max':
upper_name = name.upper()
upper_names.append(upper_name)
print(upper_names);
# ['CHRIS', 'KARIS', 'VICTOR']
This code behaves like the version that uses continue
but is a little more concise.
Why bother using continue
if we can write looping logic without it? You don't have to use continue
, of course. However, it often leads to a more elegant solution to a problem. Without continue
, your loops can get cluttered with nested conditional logic.
for value in collection:
if some_condition():
# some code here
if another_condition():
# some more code here
We can use continue
to rewrite this loop without the nested if
s:
for value in collection:
if not some_condition():
continue
# some code here
if not another_condition():
continue
# some more code here
Which of these is more readable and easier to maintain? That's not always easy to answer. As is often the case, the choice may be made for you by local coding standards. At other times, it's a matter of deciding which version looks and reads better.
The continue
statement tells Python to start the next iteration of the nearest enclosing loop. You can't start a new iteration of an outer loop if you're currently in an inner (nested) loop.
Instead of exiting the current iteration, you sometimes want to stop iterating completely. For instance, when you search a list for a specific value, you probably want to stop searching once you find it. There's no reason to keep searching if you don't need any subsequent matches.
Let's explore this idea with some code. Create a file named search.py
with the following code and run it from your terminal:
numbers = [3, 1, 5, 9, 2, 6, 4, 7]
found_item = -1
index = 0
while index < len(numbers):
if numbers[index] == 5:
found_item = index
index += 1
print(found_item)
This program iterates over the elements of a list to find the element whose value is 5
. It saves the index
value in found_item
. However, the loop continues to iterate after finding the desired value. That seems pointless and wasteful. It's where break
steps in and saves the day:
numbers = [3, 1, 5, 9, 2, 6, 4, 7]
found_item = -1
index = 0
while index < len(numbers):
if numbers[index] == 5:
found_item = index
# highlight
break
# endhighlight
index += 1
print(found_item)
The break
statement tells Python to terminate the nearest enclosing loop once we find the desired element. You can't break out of an outer loop if you're currently in an inner (nested) loop.
A typical while
loop looks something like this:
while some condition is truthy
do some work
In most code, this is a perfectly fine solution; you usually want to skip over the "do some work" part if "some condition" is initially falsy. However, sometimes you want to "do some work" at least once, even if the condition is initially falsy. For that, you need something like this:
do some work
while some condition is truthy
In other words, you always want to "do some work" at least once. This is often called a do/while or do/until loop since many languages have a looping structure with those names. Python does not, so you must take a different approach. That typically uses a break
statement at the end of the loop, like so:
do some work
if some condition is falsy
break
This often comes up in interactive programs where you may want to execute the main program loop at least once before exiting. The code to handle such a loop might look something like this:
keep_going = True
while keep_going:
# main loop code is here
answer = input('Play again? (y/n) ')
if answer == 'n':
keep_going = False
That's workable, albeit a little awkward. A slightly less clumsy alternative uses break
and while True
.
while True:
# main loop code is here
answer = input('Play again? (y/n) ')
if answer == 'n':
break
We sometimes need to iterate through multiple collections in parallel. That is, we want to grab data from several collections during each loop iteration. If all the collections are indexed sequences, you can write a while
loop using indexes. However, that's error-prone and often messy.
Meet the hero of parallel iteration: zip
! The zip
function is specifically designed to make simultaneous iteration easy. Let's see what this looks like.
Suppose you need to print the full names of several thousand people. For bizarre historical reasons, you have two lists: one contains the forenames, and the other contains the corresponding surnames. Without zip
, you need to use a while
loop and indexing. That's feasible, so let's try it. We'll use 4 names to test the concept:
forenames = ['Ken', 'Lynn', 'Pat', 'Nancy']
surnames = ['Camp', 'Blake', 'Flanagan', 'Short']
index = 0
while index < len(forenames):
if index >= len(surnames): # surnames might be shorter.
break
forename = forenames[index]
surname = surnames[index]
print(f'{forename} {surname}')
index += 1
Ken Camp
Lynn Blake
Pat Flanagan
Nancy Short
While it's not horrid, it's a lot of work. We can do better than that with zip
:
forenames = ['Ken', 'Lynn', 'Pat', 'Nancy']
surnames = ['Camp', 'Blake', 'Flanagan', 'Short']
zipped_names = zip(forenames, surnames)
for forename, surname in zipped_names:
print(f'{forename} {surname}')
That definitely looks better! However, we should explain lines 4 and 5. On line 4, zip
creates a lazy sequence that acts like a list of tuples. Each tuple contains a forename and a surname. Line 5 is the start of our loop. We're taking advantage of the fact that for
can assign multiple variables when the collection elements are tuples. Thus, we can grab the forename and surname from the current tuple.
Note that zip
takes care of the potential problem of dealing with lists of different sizes. Our first attempt needed that if
statement in the while
block to guard against the surnames
list being shorter than forenames
.
Python supports a concise and readable way to create mutable collections from existing iterable collections: comprehensions. There are 3 comprehension types: list, dict, and set. Properly used, comprehensions can simplify your code and make it easier to understand.
Unlike most for
and while
loops, which are statements, comprehensions are expressions. You can use a comprehension on the right side of an assignment, as a function argument, as a return value, or any other place where you can use an expression that evaluates as a list, dict, or set. You can even use them as standalone expressions:
[print(foo) for foo in collection]
However, an ordinary for
loop is the preferred alternative.
The most commonly used comprehensions are list comprehensions. They take an iterable collection and create a new list through iteration and optional selection. List comprehensions have the following format:
[ expression for element in iterable if condition ]
The if condition
portion is optional: it tells Python to select only certain elements from the iterable
. The for element in iterable
portion describes the iteration: it looks exactly like a for
loop. It can be read in much the same way. Finally, the expression
is a value that gets returned by each iteration of the loop. Python collects all the return values and puts them in a new list.
The expression
in a comprehension often performs a transformation. It determines a new value based on an element from the original collection. Such comprehensions are called transformations.
If the if condition
portion is present, we say that the comprehension also performs selection. With selections, it's not uncommon to return the original values from the collection:
[ element for element in iterable if condition ]
The terms transformation and selection are handy to remember. You will encounter them in many languages.
Consider this basic example of a transformative list comprehension:
squares = [ number * number for number in range(5) ]
print(squares) # [0, 1, 4, 9, 16]
Here, we're iterating over the numbers in the indicated range: 0, 1, 2, 3, 4
. We compute the square with number * number
for each number. Finally, Python collects all the squares into a list and assigns the list to the squares
variable. Voila! We now have a list of squares.
Of course, we could do the same thing with an ordinary for
loop:
squares = []
for number in range(5):
square = number * number
squares.append(square)
print(squares) # [0, 1, 4, 9, 16]
That's quite a bit more code, and the effort of reading it is a bit higher.
Let's look at a selection example:
multiples_of_6 = [ number for number in range(20)
if number % 6 == 0 ]
print(multiples_of_6) # [0, 6, 12, 18]
Here, we see the pattern of using the original collection values as the expression. We've also split the comprehension over two lines, which can aid readability.
This example combines selection and transformation:
even_squares = [ number * number
for number in range(10)
if number % 2 == 0 ]
print(even_squares) # [0, 4, 16, 36, 64]
This code selects all of the even numbers in the specified range and then returns a list of the squares of the chosen numbers.
Let's try iterating over a dictionary. Suppose we have a dict of cats. We're using the cat names as keys; each name is associated with the cat's coat color. We want to create a list of the names in uppercase:
cats_colors = {
'Tess': 'brown',
'Leo': 'orange',
'Fluffy': 'gray',
'Ben': 'black',
'Kat': 'orange',
}
names = [ name.upper() for name in cats_colors ]
print(names)
# ['TESS', 'LEO', 'FLUFFY', 'BEN', 'KAT']
This is nearly identical to using the dict.keys
method. The only difference is that the list comprehension returns an ordinary list, not a dictionary view object.
You can also iterate over the values by iterating in cats_colors.values()
or the key/value pairs by iterating in cats_colors.items()
.
Suppose we now want to limit the result list to just orange cats. We can accomplish that by adding a selection criterion to the comprehension:
cats_colors = {
'Tess': 'brown',
'Leo': 'orange',
'Fluffy': 'gray',
'Ben': 'black',
'Kat': 'orange',
}
names = [ name.upper()
for name in cats_colors
if cats_colors[name] == 'orange' ]
print(names) # ['LEO', 'KAT']
It's also possible to use multiple selection criteria. Let's limit the result to cats whose name begins with L
:
cats_colors = {
'Tess': 'brown',
'Leo': 'orange',
'Fluffy': 'gray',
'Ben': 'black',
'Kat': 'orange',
}
names = [ name.upper()
for name in cats_colors
if cats_colors[name] == 'orange'
if name[0] == 'L' ]
print(names) # ['LEO']
Multiple selection criteria act like nested if
statements or as and
-ed conditions. The selections combine, so only collection members matching all criteria are selected.
Comprehensions can also have multiple for
loop components. For instance, let's generate a deck of cards based on a list of the suits and a list of the ranks:
suits = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
ranks = [
'2', '3', '4', '5', '6', '7', '8', '9', '10',
'Jack', 'Queen', 'King', 'Ace',
]
deck = [ f'{rank} of {suit}'
for suit in suits
for rank in ranks ]
print(deck)
Compare this code with the one shown earlier in Nested Loops. As multiple selection criteria resemble a nested if
, multiple looping components resemble a nested for
loop.
Dictionary comprehensions are almost identical to list comprehensions. However, they create new dictionaries instead of lists. Syntactically, they use curly braces instead of square brackets, and the expression becomes a "key: value" pair. Dictionary comprehensions have the following format:
{ key: value for element in iterable if condition }
The most significant difference is that the expression
component is now a key/value pair, each given by another expression.
Consider this basic example of a dictionary comprehension:
squares = { f'{number}-squared': number * number
for number in range(1, 6) }
print(squares)
# pretty-printed for clarity.
{
'1-squared': 1,
'2-squared': 4,
'3-squared': 9,
'4-squared': 16,
'5-squared': 25
}
Other than the differences mentioned above, list and dict comprehensions are identical.
Set comprehensions look almost identical to dict comprehensions. However, they create a new set instead of a dict and only have one expression to the left of the word for
:
{ expression for element in iterable if condition }
Previously, a colon-separated key/value expression pair was to the left of for
. Now, we only have an expression.
Consider this basic example of a set comprehension:
squares = { number * number for number in range(1, 6) }
print(squares) # {1, 4, 9, 16, 25}
Other than the differences mentioned above, dict and set comprehensions are identical.
Python programmers often wonder why Python doesn't have tuple comprehensions. They can easily see that the following valid code hints that it might be a tuple comprehension:
squares = ( number * number for number in range(1, 6) )
print(squares) # <generator object <genexpr> at 0x104e39a40>
However, it is not a comprehension. Instead, it is a generator expression. We won't discuss generators in this book, but you'll see them later in the Core Curriculum. You'll also learn why they are helpful. The fact that a generator expression looks like it should be tuple comprehension is merely a matter of syntax. It doesn't answer the question of why.
Comprehensions don't build their results all at once. Each kind of comprehension works something like this:
result = empty_collection # [], {}, set()
for item in collection:
result.append(item)
As you can see, our result
starts as an empty collection. We then modify the result
collection during each iteration by appending a new item to result
. From this, it's clear that the result must be a mutable type. Tuples are immutable, so Python can't have tuple comprehensions.
Since ranges and strings are also immutable, comprehensions can't create them. If you must have a tuple or string, use the tuple
or str
constructors to convert a list comprehension's result into a tuple or string. (Sorry, but you can't do something similar to create a range.)
Loops and comprehensions are a great way to perform repeated operations on a collection. In Python, you'll often find yourself reaching for a comprehension before a loop, but not always. Let's test these concepts with some exercises!
The following code causes an infinite loop (a loop that never stops iterating). Why?
counter = 0
while counter < 5:
print(counter)
The problem occurs in the loop body. We never increment counter
, so counter < 5
always returns a truthy value.
Video Walkthrough
Modify the age.py
program you wrote in Exercise 3 of the Input/Output chapter. The updated code should use a for
loop to display the future ages.
age = int(input('How old are you? '))
print(f'You are {age} years old.')
print()
for future in range(10, 50, 10):
print(f'In {future} years, you will be '
f'{age + future} years old.')
Video Walkthrough
Use a while
loop to print the numbers in my_list
, one number per line. Then, do the same with a for
loop.
my_list = [6, 3, 0, 11, 20, 4, 17]
6
3
0
11
20
4
17
my_list = [6, 3, 0, 11, 20, 4, 17]
index = 0
while index < len(my_list):
number = my_list[index]
print(number)
index += 1
my_list = [6, 3, 0, 11, 20, 4, 17]
for number in my_list:
print(number)
Our solution using a while
loop uses indexing to control iteration and to access the list members. Note that we start by setting index
to 0
and then iterate while index
is less than the list length.
The solution using a for
loop is clearly easier to understand -- we don't have to mess around with indexing; we only need to iterate over the list elements.
Video Walkthrough
Use a while
loop to print all numbers in my_list
with even values, one number per line. Then, print the odd numbers using a ' for' loop.
my_list = [6, 3, 0, 11, 20, 4, 17]
6
0
20
4
3
11
17
my_list = [6, 3, 0, 11, 20, 4, 17]
index = 0
while index < len(my_list):
number = my_list[index]
# Even numbers are exactly divisible by 2
if number % 2 == 0:
print(number)
index += 1
my_list = [6, 3, 0, 11, 20, 4, 17]
for number in my_list:
# Odd numbers are not exactly divisible by 2
if number % 2 != 0:
print(number)
Our solutions both rely on using the %
operator to determine whether a number is exactly divisible by 2. Even numbers are; odd numbers aren't. In both cases, we only need to compare the result of element % 2
with 0
. If number % 2
is 0
, the number is even. Otherwise, it is odd.
As with the previous problem, we needed indexing to control the while
loop. However, the for
loop led to code that is easier to read.
Video Walkthrough
Print all of the even numbers in the following list of nested lists. Don't use any while
loops.
my_list = [
[1, 3, 6, 11],
[4, 2, 4],
[9, 17, 16, 0],
]
6
4
2
4
16
0
my_list = [
[1, 3, 6, 11],
[4, 2, 4],
[9, 17, 16, 0],
]
for nested_list in my_list:
for number in nested_list:
if number % 2 == 0:
print(number)
That may not have been too easy. Nested loops are hard to think about, but you'll encounter them often enough to have dreams about them. We start by iterating over the nested lists inside my_list
. That means nested_list
takes on the values [1, 3, 6, 11]
, [4, 2, 4]
, and [9, 7, 16, 0]
as the iteration proceeds. We then iterate over the numbers in the current nested list during each iteration. Finally, we print the even numbers.
Video Walkthrough
Let's try another variation on the even/odd-numbers theme.
We'll return to the simpler one-dimensional version of my_list
. In this problem, you should write code that creates a new list with one element for each number in my_list
. If the original number is an even, then the corresponding element in the new list should contain the string 'even'
; otherwise, the element should contain 'odd'
.
my_list = [
1, 3, 6, 11,
4, 2, 4, 9,
17, 16, 0,
]
# pretty-printed for clarity
[
'odd', 'odd', 'even', 'odd',
'even', 'even', 'even', 'odd',
'odd', 'even', 'even'
]
my_list = [
1, 3, 6, 11,
4, 2, 4, 9,
17, 16, 0,
]
result = []
for number in my_list:
if number % 2 == 0:
result.append('even')
else:
result.append('odd')
print(result)
Our approach is straightforward: we iterate over all the numbers in the list and check whether each is even. Based on the result, we append either 'even'
or 'odd'
to the result
list.
You may have struggled if you tried to use a list comprehension for this problem. Since comprehensions don't have an else
capability, trying to generate 'even'
for some values and 'odd'
for others is challenging. You can use a ternary expression in the comprehension, but this is a little confusing visually:
my_list = [
1, 3, 6, 11,
4, 2, 4, 9,
17, 16, 0,
]
#highlight
result = [ 'even' if number % 2 == 0 else 'odd'
for number in my_list ]
#endhighlight
print(result)
On line 7, we've used a ternary expression to choose between the two values. The ternary is equivalent to:
if number % 2 == 0:
return 'even'
else:
return 'odd'
A cleaner approach is to use a helper function to determine whether we should add 'even'
or 'odd'
to the new list:
my_list = [
1, 3, 6, 11,
4, 2, 4, 9,
17, 16, 0,
]
#highlight
def odd_or_even(number):
return 'even' if number % 2 == 0 else 'odd'
result = [ odd_or_even(number)
#endhighlight
for number in my_list ]
print(result)
Video Walkthrough
Write a find_integers
function that returns a list of all the integers from my_tuple
:
my_tuple = (1, 'a', '1', 3, [7], 3.1415,
-4, None, {1, 2, 3}, False)
integers = find_integers(my_tuple)
print(integers) # [1, 3, -4]
You can use the expression type(object) is int
to determine whether an object is an integer. For instance:
print(type(True) is int) # False (boolean)
print(type([1, 2, 3]) is int) # False (list)
print(type(3.141592) is int) # False (float)
print(type(77) is int) # True
You may receive a SyntaxWarning
warning message from the last two examples. You can ignore that warning.
def find_integers(things):
return [ element
for element in things
if type(element) is int ]
my_tuple = (1, 'a', '1', 3, [7], 3.1415,
-4, None, {1, 2, 3}, False)
integers = find_integers(my_tuple)
print(integers) # [1, 3, -4]
Our solution uses a list comprehension to iterate through the elements in the things
argument and create a new list.
It's worth noting that we used a list comprehension to iterate over the tuple. The main reason for that choice is that find_integers
is expected to return a list, not a tuple. However, an even more important reason is that there is no such thing as a "tuple comprehension". Comprehensions don't care what kind of collection you're iterating, but the result must always be a list, set, or dictionary.
Video Walkthrough
Write a comprehension that creates a dict
object whose keys are strings and whose values are the length of the corresponding key. Only keys with odd lengths should be in the dict. Use the set given by my_set
as the source of strings.
my_set = {
'Fluffy',
'Butterscotch',
'Pudding',
'Cheddar',
'Cocoa',
}
my_set = {
'Fluffy',
'Butterscotch',
'Pudding',
'Cheddar',
'Cocoa',
}
result = { name: len(name)
for name in my_set
if len(name) % 2 != 0 }
print(result)
# {'Cheddar': 7, 'Pudding': 7, 'Cocoa': 5}
Remember: sets are unordered, so your result may have a different ordering.
Video Walkthrough
Don't let the math scare you. This is a logic and syntax problem, not a math problem.
Write a function that computes and returns the factorial of a number by using a for
or while
loop. The factorial of a positive integer n
, signified by n!
, is defined as the product of all integers between 1
and n
, inclusive:
n! | Expansion | Result |
---|---|---|
1! |
1 |
1 |
2! |
1 * 2 |
2 |
3! |
1 * 2 * 3 |
6 |
4! |
1 * 2 * 3 * 4 |
24 |
5! |
1 * 2 * 3 * 4 * 5 |
120 |
You may assume that the argument is always a positive integer.
print(factorial(1)) # 1
print(factorial(2)) # 2
print(factorial(3)) # 6
print(factorial(4)) # 24
print(factorial(5)) # 120
print(factorial(6)) # 720
print(factorial(7)) # 5040
print(factorial(8)) # 40320
print(factorial(25)) # 15511210043330985984000000
def factorial(n):
result = 1
while n > 0:
result *= n
n -= 1
return result
def factorial(n):
result = 1
for number in range(n, 0, -1):
result *= number
return result
Our first solution uses a while
loop to compute the return value. We begin by assigning the result
variable to 1
, then we multiply result
by all of the integers between n
and 1
, inclusive. Note that we need to decrement n
by 1
at the end of each iteration.
The second solution is similar, but it uses a for
loop instead of a while
loop. The benefit of using for
is that we don't need to worry about decrementing n
. Instead, we just iterate over the integers between n
and 1
, inclusive, using a range.
Video Walkthrough
The following code uses the randrange
function from Python's random
library to obtain and print a random integer within a given range. Using a while
loop, it keeps running until it finds a random number that matches the last number in the range. Refactor the code so it doesn't require two different invocations of randrange
.
import random
highest = 10
number = random.randrange(highest + 1)
print(number)
while number != highest:
number = random.randrange(highest + 1)
print(number)
import random
highest = 10
while True:
number = random.randrange(highest + 1)
print(number)
if number == highest:
break
The ideal do/while loop use case occurs when you need to execute some code at least once. We need to do that here. However, Python doesn't have a do/while loop. Instead, we can combine a while True
loop with a break
statement.
Video Walkthrough
Challenging Problem: Don't feel bad if you struggle with this problem or can't solve it. The problem is not easy. It is designed to demonstrate why we prefer to use for
loops when we can, and a big part of that problem is messy code that is difficult to write and understand. See how far you can get, but don't spend much time struggling.
Print all of the even numbers in the following list of nested lists. This time, don't use any for
loops.
my_list = [
[1, 3, 6, 11],
[4, 2, 4],
[9, 17, 16, 0],
]
6
4
2
4
16
0
my_list = [
[1, 3, 6, 11],
[4, 2, 4],
[9, 17, 16, 0],
]
outer_index = 0
while outer_index < len(my_list):
inner_index = 0
while inner_index < len(my_list[outer_index]):
number = my_list[outer_index][inner_index]
if number % 2 == 0:
print(number)
inner_index += 1
outer_index += 1
Phew! That's messy and hard to follow, isn't it? While the individual loops aren't tough to understand, keeping track of two different indexes and getting them to work together is no mean feat. We also have to use both indexes on lines 10 and 11.
We can simplify this solution a bit by assigning each nested list to a local variable:
my_list = [
[1, 3, 6, 11],
[4, 2, 4],
[9, 17, 16, 0],
]
outer_index = 0
while outer_index < len(my_list):
inner_list = my_list[outer_index]
inner_index = 0
while inner_index < len(inner_list):
number = inner_list[inner_index]
if number % 2 == 0:
print(number)
inner_index += 1
outer_index += 1
Video Walkthrough