for loops and the range function

Documentation

  1. Python Tutorial
    1. for loop
    2. range function
  2. Python Language Reference
    1. A for loop can loop through any iterable object.
    2. range function
    3. range class
  3. Python Glossary
    1. iterable. I think a better name for iterable would be “forable”.
    2. iterator. We haven’t seen an example of an iterator yet, but we will.
    3. sequence. A sequence is an example of an iterable.

How not to program

I’m starting at 0, not 1, because the indices of a sequence type such as a string or list begin at 0. For example, the first character in a string is said to be character number 0.

nottoprogram1.py

0
1
2
3
4
5
6
7
8
9

A while loop

while.py

The indented statements will be repeated over and over as long as it is still true that i < 10. Indent each indented statement with exactly four spaces.

0
1
2
3
4
5
6
7
8
9

Two ways to produce the same output

The three vital statistics of a loop are

  1. the starting point, in this case 0
  2. the ending point, in this case 9
  3. the stride (or step), i.e., how much to add to the variable during each iteration, in this case 1

In a while loop, the three vital statistics are on three separate lines. In a for loop, the three vital statistics are on the same line. We therefore say that the for loop is more localized.

range(10) means the same thing as range(0, 10) and range(0, 10, 1). We can write the expression range(10) to the right of the keyword in because the value of this expression is an iterable object.

What is an iterable object? For the time being, we’re just going to define it as “something you can write to the right of the keyword in“, although I admit that this definition is circular. A better definition might be “something that, when properly stimulated, gives you a sequence of values”. The for loop and the keyword in provide that stimulation. In the current program, the sequence of values is the sequence of 10 consecutive ints from 0 to 9 inclusive.

for.py

0
1
2
3
4
5
6
7
8
9

0
1
2
3
4
5
6
7
8
9

The value of i after the for loop is over is 9.

Play with the range function in IDLE

The built-in function list converts its argument into an actual list, just like the built-in function int converts its argument into an int.

>>> range(0, 10)
range(0, 10)                Not very illuminating.

>>> type(range(0, 10))      What type of thing is a range?
<class 'range'>

>>> len(range(0, 10))       We applied this function to a string.
10

>>> list(range(0, 10))      Convert the range into a list.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(range(10))         Let’s try it with one argument.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(range(0, 10, 2))   Let’s try it with three arguments.
[0, 2, 4, 6, 8]

>>> list(range(0, 10, .5))  Let’s try it with a fractional third argument.
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    list(range(0, 10, .5))
TypeError: 'float' object cannot be interpreted as an integer

When we get to Iterators, we’ll be able to have a fractional step by making an iterable.

Prove that the return value of the range function is iterable

try:
    it = iter(range(10))
except TypeError as error:
    print("not iterable:", error)
else:
    print("iterable")
iterable

Is a string iterable? If so, that would be another piece of evidence that a range is similar to a string.

    it = iter("hello")

See The only reliable way.

How many variables do we need?

pierogies.py

8 pierogies feed 4 people or 2 Hungarians.
12 pierogies feed 6 people or 3 Hungarians.
16 pierogies feed 8 people or 4 Hungarians.
20 pierogies feed 10 people or 5 Hungarians.
24 pierogies feed 12 people or 6 Hungarians.
28 pierogies feed 14 people or 7 Hungarians.
32 pierogies feed 16 people or 8 Hungarians.

Pad each number with a leading blank, if necessary, to make it at least two characters wide.

    print(f"{4 * h:2} pierogies feed {2 * h:2} people or {h} Hungarians.")
 8 pierogies feed  4 people or 2 Hungarians.
12 pierogies feed  6 people or 3 Hungarians.
16 pierogies feed  8 people or 4 Hungarians.
20 pierogies feed 10 people or 5 Hungarians.
24 pierogies feed 12 people or 6 Hungarians.
28 pierogies feed 14 people or 7 Hungarians.
32 pierogies feed 16 people or 8 Hungarians.

How many variables do we need:
the New York State Thruway

thruway.py

We saw a triple-quoted string here.

+--------------+
| Albany   108 |
| Montreal 328 |
| Buffalo  388 |
+--------------+

+--------------+
| Albany    98 |
| Montreal 318 |
| Buffalo  378 |
+--------------+

+--------------+
| Albany    88 |
| Montreal 308 |
| Buffalo  368 |
+--------------+

+--------------+
| Albany    78 |
| Montreal 298 |
| Buffalo  358 |
+--------------+

+--------------+
| Albany    68 |
| Montreal 288 |
| Buffalo  348 |
+--------------+

+--------------+
| Albany    58 |
| Montreal 278 |
| Buffalo  338 |
+--------------+

+--------------+
| Albany    48 |
| Montreal 268 |
| Buffalo  328 |
+--------------+

It’s more readable to create the big string outside of the loop. Then plug the three numbers into the big string by calling the format method of the big string:

#A string containing 5 lines, each ending with a newline character.

sign = """\
+--------------+
| Albany   {:3} |
| Montreal {:3} |
| Buffalo  {:3} |
+--------------+
"""

for a in range(108, 38, -10):
    print(sign.format(a, a + 220, a + 280))

King James I of England was the same person as King James VI of Scotland. Similarly, King James II of England was the same person as King James VII of Scotland. And there almost was a King James III of England, who was the same person as King James VIII of Scotland. Get the pattern, like Albany and Montreal? Can you turn it into a Python program?

How many variables do we need?
(another example)

The letter A comes before the letter B in alphabetical order because the ASCII (vs. EBCDIC) code number of A (decimal 65) comes before the code number of B (decimal 66). We can extend this alphabetical order to all the other characters. For example, the digit 0 comes before the digit 1 in alphabetical order because the code number of the character 0 (decimal 48) comes before the code number of the character 1 (decimal 49), and the comma , comes before the period . in alphabetical order because the code number of the character , (decimal 44) comes before the code number of the character . (decimal 46).

asciichart.py

    decimal    octal       hex   binary
  (base 10) (base 8) (base 16) (base 2)
         32       40        20 00100000
!        33       41        21 00100001
"        34       42        22 00100010
#        35       43        23 00100011
$        36       44        24 00100100
%        37       45        25 00100101
&        38       46        26 00100110
'        39       47        27 00100111
(        40       50        28 00101000
)        41       51        29 00101001
*        42       52        2A 00101010
+        43       53        2B 00101011
,        44       54        2C 00101100
-        45       55        2D 00101101
.        46       56        2E 00101110
/        47       57        2F 00101111
0        48       60        30 00110000
1        49       61        31 00110001
2        50       62        32 00110010
3        51       63        33 00110011
4        52       64        34 00110100
5        53       65        35 00110101
6        54       66        36 00110110
7        55       67        37 00110111
8        56       70        38 00111000
9        57       71        39 00111001
:        58       72        3A 00111010
;        59       73        3B 00111011
<        60       74        3C 00111100
=        61       75        3D 00111101
>        62       76        3E 00111110
?        63       77        3F 00111111
@        64      100        40 01000000
A        65      101        41 01000001
B        66      102        42 01000010
C        67      103        43 01000011
D        68      104        44 01000100
E        69      105        45 01000101
F        70      106        46 01000110
G        71      107        47 01000111
H        72      110        48 01001000
I        73      111        49 01001001
J        74      112        4A 01001010
K        75      113        4B 01001011
L        76      114        4C 01001100
M        77      115        4D 01001101
N        78      116        4E 01001110
O        79      117        4F 01001111
P        80      120        50 01010000
Q        81      121        51 01010001
R        82      122        52 01010010
S        83      123        53 01010011
T        84      124        54 01010100
U        85      125        55 01010101
V        86      126        56 01010110
W        87      127        57 01010111
X        88      130        58 01011000
Y        89      131        59 01011001
Z        90      132        5A 01011010
[        91      133        5B 01011011
\        92      134        5C 01011100
]        93      135        5D 01011101
^        94      136        5E 01011110
_        95      137        5F 01011111
`        96      140        60 01100000
a        97      141        61 01100001
b        98      142        62 01100010
c        99      143        63 01100011
d       100      144        64 01100100
e       101      145        65 01100101
f       102      146        66 01100110
g       103      147        67 01100111
h       104      150        68 01101000
i       105      151        69 01101001
j       106      152        6A 01101010
k       107      153        6B 01101011
l       108      154        6C 01101100
m       109      155        6D 01101101
n       110      156        6E 01101110
o       111      157        6F 01101111
p       112      160        70 01110000
q       113      161        71 01110001
r       114      162        72 01110010
s       115      163        73 01110011
t       116      164        74 01110100
u       117      165        75 01110101
v       118      166        76 01110110
w       119      167        77 01110111
x       120      170        78 01111000
y       121      171        79 01111001
z       122      172        7A 01111010
{       123      173        7B 01111011
|       124      174        7C 01111100
}       125      175        7D 01111101
~       126      176        7E 01111110

IDLE can’t display emojis. We therefore print the emojis in the terminal window, not in IDLE, to avoid the error
UnicodeEncodeError: 'UCS-2' codec can't encode characters in position 6-6: Non-BMP character not supported in Tk.

The code number of the character 😀 is 128,512. Written in hexadecimal, this number is 1F600. Here is how I used IDLE to convert from decimal to hexadecimal and back again.

>>> f"{128512:X}"
'1F600'

>>> 0x1F600
128512
"""
Print the smiley face emojis, one per line.
"""

import sys

print("   dec   hex")

for i in range(0x1F600, 0x1F650):   #or for i in range(128_512, 128_592):
    print(f"{i:6} {i:05X} {i:c}")

sys.exit(0)
python3 myprog.py
   dec   hex
128512 1F600 😀
128513 1F601 😁
128514 1F602 😂
128515 1F603 😃
128516 1F604 😄
etc.
128587 1F64B 🙋
128588 1F64C 🙌
128589 1F64D 🙍
128590 1F64E 🙎
128591 1F64F 🙏

Counting down

See also the beer.py that was installed when you installed Python.

find / -type f -name beer.py 2> /dev/null
/Library/Frameworks/Python.framework/Versions/3.7/share/doc/python3.7/examples/Tools/demo/beer.py
cd C:\
dir /b /s beer.py
C:\Users\Myname\AppData\Local\Programs\Python\Python37-32\Tools\demo\beer.py

beer.py

100 bottles of beer on the wall,
100 bottles of beer on the wall--
If one of those bottles should happen to fall,
99 bottles of beer on the wall.

99 bottles of beer on the wall,
99 bottles of beer on the wall--
If one of those bottles should happen to fall,
98 bottles of beer on the wall.

etc.

2 bottles of beer on the wall,
2 bottles of beer on the wall--
If one of those bottles should happen to fall,
1 bottles of beer on the wall.

1 bottles of beer on the wall,
1 bottles of beer on the wall--
If one of those bottles should happen to fall,
0 bottles of beer on the wall.

Call print only once per iteration:

#This format string contains four lines of text,
#each ending with the newline character.

verse = """\
{} bottles of beer on the wall,
{} bottles of beer---
If one of those bottles should happen to fall,
{} bottles of beer on the wall.
"""

for b in range(100, 0, -1):
    print(verse.format(b, b, b - 1))
    #time.sleep(3)

Count down and make a blastoff.

An HTML stylesheet

fontsize.py outputs the web page fontsize.html. See pangrams.

<HTML>
<BODY>
<H1>Stylesheet</H1>

<P STYLE = "font-size: 1pt;">
1 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 2pt;">
2 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 3pt;">
3 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 4pt;">
4 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 5pt;">
5 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 6pt;">
6 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 7pt;">
7 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 8pt;">
8 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 9pt;">
9 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 10pt;">
10 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 11pt;">
11 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

<P STYLE = "font-size: 12pt;">
12 point
<BR>
Pack my box with five dozen liquor jugs.
</P>

</BODY>
</HTML>

To render the output in HTML, save the standard output of fontsize.py in a new file named fontsize.html. On MacOS,

python3 fontsize.py > fontsize.html
ls -l fontsize.html

On Microsoft Windows,

fontsize.py > fontsize.html
dir fontsize.html

Then open your new fontsize.html file with a web browser. On macOS, control-click on fontsize.html and select
Open With → Google Chrome.app
It will look like this: fontsize.html

We could also have used the format method:

#This format string contains six lines of text.
#The last line is empty (i.e., consists only of one newline character).

paragraph = """\
<P STYLE = "font-size: {}pt;">
{} point
<BR>
Pack my box with five dozen liquor jugs.
</P>
"""

for point in range(1, 13):
    print(paragraph.format(points, points))

Nested loops

We saw nested while loops here. See also the function intertools.product.

nestedloops.py

Lucy in the sky with diamonds
Lucy in the sky with diamonds
Lucy in the sky with diamonds
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

Lucy in the sky with diamonds
Lucy in the sky with diamonds
Lucy in the sky with diamonds
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

Lucy in the sky with diamonds
Lucy in the sky with diamonds
Lucy in the sky with diamonds
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

for outer in range(3):
    print(f"chorus (outer = {outer})")

    for inner in range(3):
        print(f"Lucy in the sky with diamonds (inner = {inner})")

    print(f'A{28 * "a"}h')
    print()
chorus (outer = 0)
Lucy in the sky with diamonds (inner = 0)
Lucy in the sky with diamonds (inner = 1)
Lucy in the sky with diamonds (inner = 2)
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

chorus (outer = 1)
Lucy in the sky with diamonds (inner = 0)
Lucy in the sky with diamonds (inner = 1)
Lucy in the sky with diamonds (inner = 2)
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

chorus (outer = 2)
Lucy in the sky with diamonds (inner = 0)
Lucy in the sky with diamonds (inner = 1)
Lucy in the sky with diamonds (inner = 2)
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaah

By the way, we can also produce the original output using string multiplication and string addition:

import sys

moan = "A" + 28 * "a" + "h"
paragraph = 3 * "Lucy in the sky with diamonds\n" + moan + "\n\n"
print(3 * paragraph, end = "")

sys.exit(0)

Nested loops: draw a rectangle

The end keyword argument of print keeps all the X’s on the same line of output. But every line of output should end with a newline character, so that’s why this program also makes a no-argument call to print.

line.py

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

rectangle.py

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Draw graph paper

Let the user input four numbers to design a sheet of graph paper. For simplicity, let the right and bottom edges remain ragged. Here are two examples:

How many rows of boxes (e.g., 3)? 3
How many columns of boxes (e.g., 4)? 4
How many rows of spaces in each box (e.g., 1)? 1
How many columns of spaces in each box (e.g., 3)? 4

+----+----+----+----
|    |    |    |
+----+----+----+----
|    |    |    |
+----+----+----+----
|    |    |    |
How many rows of boxes (e.g., 3)? 2
How many columns of boxes (e.g., 4)? 4
How many rows of spaces in each box (e.g., 1)? 3
How many columns of spaces in each box (e.g., 3)? 8

+--------+--------+--------+--------
|        |        |        |
|        |        |        |
|        |        |        |
+--------+--------+--------+--------
|        |        |        |
|        |        |        |
|        |        |        |

Extra credit: close the right and bottom edges.

How many rows of boxes (e.g., 3)? 2
How many columns of boxes (e.g., 4)? 4
How many rows of spaces in each box (e.g., 1)? 3
How many columns of spaces in each box (e.g., 3)? 8

+--------+--------+--------+--------+
|        |        |        |        |
|        |        |        |        |
|        |        |        |        |
+--------+--------+--------+--------+
|        |        |        |        |
|        |        |        |        |
|        |        |        |        |
+--------+--------+--------+--------+

If the graph paper problem is too hard, start with a script that asks only two questions and outputs only the first line of the graph paper:

How many columns of boxes (e.g., 4)? 4
How many columns of spaces in each box (e.g., 3)? 4

+----+----+----+----

Count by twos

i = 2
while i < 10:
    print(i)
    i += 2

print("Who do we appreciate?")
print()   #Skip a line.

for i in range(2, 10, 2):
    print(i)

print("Who do we appreciate?")
2
4
6
8
Who do we appreciate?

2
4
6
8
Who do we appreciate?

List the presidential election years: 1788, 1792, 1796, …, 2012, 2016, 2020.

Count by twos to converge on the value of π

The range(1, 100, 2) contains the fifty integers 1, 3, 5, 7, 9, …, 99.

"""
Estimate the value of pi using the formula
pi = 4/1 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11 + 4/13 + ...
"""

import sys
import math

pi = 0
sign = 1

for denominator in range(1, 100, 2):
    pi += sign / denominator   #means pi = pi + sign / denominator
    sign = -sign

pi *= 4                        #means pi = pi * 4

print(f"estimated pi = {pi}")
print(f"actual pi    = {math.pi}")
print(f"error        = {abs(pi - math.pi)}")

sys.exit(0)
estimated pi = 3.121594652591011
actual pi    = 3.141592653589793
error        = 0.01999800099878213

Get more accuracy by changing 100 to 1000 (or to 10000):

estimated pi = 3.139592655589785
actual pi    = 3.141592653589793
error        = 0.001999998000008052

How many times do we have to loop until we get close to the value of π?

"""
Estimate the value of pi using the formula
pi = 4/1 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11 + 4/13 + ...
"""

import sys
import math
import itertools

pi = 0
sign = 1

for denominator in itertools.count(1, 2):  #Loop forever: 1, 3, 5, 7, 9, ...
    if abs(4 * pi - math.pi) < .001:
        break
    pi += sign / denominator   #means pi = pi + sign / denominator
    sign = -sign

pi *= 4                        #means pi = pi * 4

print("denominator = ", denominator)
print(f"estimated pi = {pi}")
print(f"actual pi    = {math.pi}")
print(f"error        = {abs(pi - math.pi)}")
print(f"It took {(denominator - 1) // 2:,} additions and subtractions to get this close.")

sys.exit(0)
estimated pi = 3.140592653839794
actual pi    = 3.141592653589793
error        = 0.000999999749998981
It took 1,000 additions and subtractions to get this close.

Things to try

  1. Change the outer while loop to a for loop in Factor.