More Loops
An Expected Failure
We have a hundred.py file here that’s meant to count down from 100.
The first line in this file is pretty long.
See if you can figure out a better way to represent the numbers from 100 to 1 while counting down.
numbers = [100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
for n in numbers:
if n > 1:
print(f"{n} more.")
else:
print(f"One more.")
print("Done.")
Range
Python’s range function allows us to get integer numbers between two numbers:
>>> for n in range(0, 5):
... print(n)
...
0
1
2
3
4
Note that the numbers go up to but not including the final value.
If the first argument is 0, we can leave it off:
>>> for n in range(5):
... print(n)
...
0
1
2
3
4
We can specify a third argument as a step:
>>> for n in range(0, 100, 10):
... print(n)
...
0
10
20
30
40
50
60
70
80
90
The range function creates special range objects in Python that contain the information needed to generate the range elements.
The elements are generated “on-the-fly”; they do not exist until needed.
>>> ten = range(10)
>>> type(ten)
<class 'range'>
>>> ten
range(0, 10)
>>> list(ten)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
We can index a range object and even take a slice of a range object, which results in another range object:
>>> ten[4]
4
>>> ten[3:8:2]
range(3, 8, 2)
>>> list(ten[3:8:2])
[3, 5, 7]
Enumerate
Sometimes you’ll see other Python programmers write code that looks like this:
We can use the enumerate function for this:
>>> numbers = [3, 4, 8]
>>> for i in range(len(numbers)):
... print(numbers[i])
...
3
4
8
What is this doing?
That i is assigned to the numbers from 0 until the number just before the length of our list.
We’re getting “indexes” for the items in our numbers list and then looking each number up. That’s a bit silly, because we could just loop over numbers:
>>> numbers = [3, 4, 8]
>>> for n in numbers:
... print(n)
...
3
4
8
Sometimes it can be useful to get indexes though. But in Python we don’t use range(len(...)) to do that. We use enumerate to do that.
We can use enumerate like this:
>>> numbers = [3, 4, 8]
>>> for item in enumerate(numbers):
... print(item)
...
(0, 3)
(1, 4)
(2, 8)
The enumerate function (it’s a class technically) makes a new object that we can iterate over to get tuples containing the index of each item and the value of the item.
While enumerate gives us an index, similar to for loops in other programming languages, it does not require us to increment this index. This happens automatically for each iteration of the loop.
Note that the returned object is not a list, but it is an iterable so we can loop over it, just like we can loop over a list:
>>> enumerate(numbers)
<enumerate object at 0x...>
>>> list(enumerate(numbers))
[(0, 3), (1, 4), (2, 8)]
Let’s write code that will print the index of each item:
>>> for item in enumerate(numbers):
... print(f"Item {item[0]} is {item[1]}")
...
Item 0 is 3
Item 1 is 4
Item 2 is 8
That’s a little awkward.
We learned about tuple unpacking and multiple assignment earlier. So we know we can do this:
>>> items = list(enumerate(numbers))
>>> items
[(0, 3), (1, 4), (2, 8)]
>>> items[0]
(0, 3)
>>> i, value = items[0]
>>> i
0
>>> value
3
The looping variable we use in a for loop is actually an assignment operation. Which means we can use tuple unpacking in for loops also.
Let’s use tuple unpacking to make that for loop more clear:
>>> for i, num in enumerate(numbers):
... print(f"Item {i} is {num}")
...
Item 0 is 3
Item 1 is 4
Item 2 is 8
Counting Exercises
Line Numbers
This is the line_numbers.py exercise in the modules directory that we saw earlier in the Introduction to Files section. Create the file line_numbers.py in the modules sub-directory of the exercises directory. To test it, run python test.py line_numbers.py from your exercises directory.
Write a program that accepts a file as its only argument and prints out the lines in the files with a line number displayed in front of them. See if you can improve the code with what we have learned in this section.
Example:
If my_file.txt contains:
This file
is two lines long.
No wait, it's three lines long!
Running:
$ python line_numbers.py my_file.txt
Should print out:
1 This file
2 is two lines long.
3 No wait, it's three lines long!
Guess in 3
Create a program guess.py in the modules sub-directory of the exercises directory. To test it, run python test.py guess.py from your exercises directory.
The program below generates a random number from 1 to 8 and gives the user 3 guesses to guess the number correctly. After each guess the program says either “Correct”, “Too high” or “Too low”.
Once a correct guess is made, the program ends. If the final guess is incorrect, the user is shown a message noting the correct number.
Your task: Refactor this code to use a for loop for readability’s sake.
import random
number = random.randint(1, 8)
print("I have selected a number between 1 and 8.")
print("You have 3 attempts to guess it correctly.")
guess = int(input("1) Enter your guess: "))
if guess < number:
print("Too low")
guess = int(input("2) Enter your guess: "))
if guess < number:
print("Too low")
guess = int(input("3) Enter your guess: "))
if guess < number:
print("Too low")
print(f"Incorrect. The correct number was {number}.")
elif guess > number:
print("Too high")
print(f"Incorrect. The correct number was {number}.")
else:
print("Correct! You've guessed the number!")
elif guess > number:
print("Too high")
guess = int(input("3) Enter your guess: "))
if guess < number:
print("Too low")
print(f"Incorrect. The correct number was {number}.")
elif guess > number:
print("Too high")
print(f"Incorrect. The correct number was {number}.")
else:
print("Correct! You've guessed the number!")
else:
print("Correct! You've guessed the number!")
elif guess > number:
print("Too high")
guess = int(input("2) Enter your guess: "))
if guess < number:
print("Too low")
guess = int(input("3) Enter your guess: "))
if guess < number:
print("Too low")
print(f"Incorrect. The correct number was {number}.")
elif guess > number:
print("Too high")
print(f"Incorrect. The correct number was {number}.")
else:
print("Correct! You've guessed the number!")
elif guess > number:
print("Too high")
guess = int(input("3) Enter your guess: "))
if guess < number:
print("Too low")
print(f"Incorrect. The correct number was {number}.")
elif guess > number:
print("Too high")
print(f"Incorrect. The correct number was {number}.")
else:
print("Correct! You've guessed the number!")
else:
print("Correct! You've guessed the number!")
else:
print("Correct! You've guessed the number!")
Hint
Use a for loop with range(3) to iterate through the attempts. You can use break to exit the loop early when the correct guess is made, and for-else to handle the case where all guesses are exhausted.
Factors
Edit the get_factors function in the ranges.py file so that it returns the factors of a given number.
To test it, run python test.py get_factors in your exercises directory.
To test locally:
>>> from ranges import get_factors
>>> get_factors(2)
[1, 2]
>>> get_factors(6)
[1, 2, 3, 6]
>>> get_factors(100)
[1, 2, 4, 5, 10, 20, 25, 50, 100]
Note
Want to test this out manually (instead of using python test.py)?
You could could create a file called get_factors_test.py with your own test code:
from ranges import get_factors
print("Calling get_factors(2)")
print("Expected: [1, 2]")
print(" Actual:", get_factors(2))
print()
print("Calling get_factors(6)")
print("Expected: [1, 2, 3, 6]")
print(" Actual:", get_factors(6))
print()
print("Calling get_factors(100)")
print("Expected: [1, 2, 4, 5, 10, 20, 25, 50, 100]")
print(" Actual:", get_factors(100))
Then you can run that file to test your code:
$ python get_factors_test.py
Range Check
Edit the is_range function in the ranges.py file so that it returns True if the given list could be represented with a range of numbers (ignoring step), and False otherwise.
To test it, run python test.py is_range in your exercises directory.
To test locally:
>>> from ranges import is_range
>>> is_range([5, 6, 7, 8])
True
>>> is_range([5, 6, 8])
False
>>> is_range([0, 2, 1, 3])
False
>>> is_range([0, 1, 2, 3])
True
Annual Return
This is the annual_return function in the ranges.py file.
This function is meant to model financial investments (whether in a bank, stock, or some other asset that increases at a particular average rate each year).
The annual_return function should accept these keyword arguments:
startis the starting amount of moneyannual_yieldis the percentage that the money will increase each year on averageyearsis the number of years for which the money will be yielding a return (whether invested or in the interest-earning account)annual_contributionis the amount of money that should be added at the end of every year.
Assuming a start value of $5,000, an annual yield of 7%, an annual contribution of $5,000, and 30 years of investment, the annual_return function should return an list like this:
>>> returns = annual_return(
... start=5000,
... annual_yield=0.07,
... years=30,
... annual_contribution=5000,
... )
>>> for amount in returns:
... print(f"${amount:,.2f}")
...
$10,350.00
$16,074.50
$22,199.72
$28,753.70
$35,766.45
$43,270.11
$51,299.01
$59,889.94
$69,082.24
$78,918.00
$89,442.26
$100,703.21
$112,752.44
$125,645.11
$139,440.27
$154,201.09
$169,995.16
$186,894.82
$204,977.46
$224,325.88
$245,028.70
$267,180.70
$290,883.35
$316,245.19
$343,382.35
$372,419.12
$403,488.45
$436,732.65
$472,303.93
$510,365.21
Note that the annual contribution is added at the end of the year, but the annual yield is computed based on the starting amount at the beginning of each year (this is compounding interest which is the way stocks and bank accounts frequently work).
Primality
Edit the is_prime function in the ranges.py file so that it returns True if a number is prime and False otherwise.
To test it, run python test.py is_prime in your exercises directory.
To test locally:
>>> from ranges import is_prime
>>> is_prime(21)
False
>>> is_prime(23)
True
Power List By Index
Edit the power_list function in the ranges.py file so that it accepts a list and returns a new list that contains each number raised to the i-th power where i is the index of that number in the given list.
To test it, run python test.py power_list in your exercises directory.
To test locally:
>>> from ranges import power_list
>>> power_list([3, 2, 5])
[1, 2, 25]
>>> numbers = [78, 700, 82, 16, 2, 3, 9.5]
>>> power_list(numbers)
[1, 700, 6724, 4096, 16, 243, 735091.890625]
Reverse Difference
This is the reverse_difference exercise in ranges.py. Edit the ranges.py file in the exercises directory to implement this exercise. To test it, run python test.py reverse_difference in your exercises directory.
Make a function reverse_difference that accepts a list of numbers and returns a new copy of the list with the reverse of the list subtracted.
Hint: Use enumerate. We will see a better way to do this later on.
Example usage:
>>> from ranges import reverse_difference
>>> reverse_difference([9, 8, 7, 6])
[3, 1, -1, -3]
>>> reverse_difference([1, 2, 3, 4, 5])
[-4, -2, 0, 2, 4]
>>> reverse_difference([3, 2, 1, 0])
[3, 1, -1, -3]
>>> reverse_difference([0, 0])
[0, 0]
with_previous
Edit the function with_previous in ranges.py so that it accepts a sequence (list, string, etc.) and returns a new list that includes a tuple of each item and the previous item (the item just before it). The first “previous item” should be None.
To test it, run python test.py with_previous in your exercises directory.
Hint: Use enumerate. We will see a better way to do this later on.
It should work like this:
>>> from ranges import with_previous
>>> numbers = [1, 2, 3, 4]
>>> with_previous(numbers)
[(1, None), (2, 1), (3, 2), (4, 3)]
Identity Matrix
File: Edit the identity function in the ranges.py file so that it takes as input a number size for the size of the matrix and returns an identity matrix of size x size elements.
To test it, run python test.py identity in your exercises directory.
An identity matrix is a square matrix with ones on the main diagonal and zeros elsewhere. A 3 by 3 identity matrix looks like:
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
To test locally:
>>> from ranges import identity
>>> identity(3)
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
>>> identity(4)
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
>>> identity(2)
[[1, 0], [0, 1]]
Matrix Addition
This is the matrix_add exercise in ranges.py. Edit the ranges.py file in the exercises directory to implement this exercise. To test it, run python test.py matrix_add in your exercises directory.
Edit the function matrix_add to accept two matrices (lists of lists of numbers) and return one matrix that includes each corresponding number in the two lists added together.
Hint: Use enumerate. We will see a better way to do this later on.
You should assume the lists of lists provided will always be the same size/shape.
>>> from ranges import matrix_add
>>> m1 = [[1, 2], [3, 4]]
>>> m2 = [[5, 6], [7, 8]]
>>> matrix_add(m1, m2)
[[6, 8], [10, 12]]
>>> m1 = [[1, 2, 3], [0, 4, 2]]
>>> m2 = [[4, 2, 1], [5, 7, 0]]
>>> matrix_add(m1, m2)
[[5, 4, 4], [5, 11, 2]]
Pythagorean Triples
Edit the triples function in the ranges.py file so that it takes a number and returns a list of tuples of 3 integers where each tuple is a Pythagorean triple, and the integers are all less than the input number.
To test it, run python test.py triples in your exercises directory.
A Pythagorean triple is a group of 3 integers a, b, and c, such that they satisfy the formula a**2 + b**2 = c**2
To test locally:
>>> from ranges import triples
>>> triples(15)
[(3, 4, 5), (5, 12, 13), (6, 8, 10)]
>>> triples(30)
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12, 15), (10, 24, 26), (12, 16, 20), (15, 20, 25), (20, 21, 29)]