Notebook 5 - More Strings and Loops¶
You can make a copy of this notebook by selecting File->Save a copy in Drive from the menu bar above.
Things you'll learn in this lesson:
- more about strings
- while loops and for loops
- break and continue statements
Strings Revisited¶
The len
Function¶
len(string)
returns the length of a passed string
Examples:
print(len('To be or not to be'))
print(len('To be or not to be\n'))
print(len('Tobeornottobe'))
print(len(''))
print(len('To be or not to be'))
print(len('To be or not to be\n'))
print(len('Tobeornottobe'))
print(len(''))
Indexing¶
You can refer to a particular character in a string with this syntax:
string[offset]
Indexing works with variables and literal strings.
In Python, offsets in strings always start counting at zero.
Examples:
mystr = 'marc'
print(mystr[0])
print(mystr[1])
print(mystr[4])
print(mystr[len(mystr)-1])
print('marc'[2])
mystr = 'marc'
print(mystr[0])
print(mystr[1])
print(mystr[4])
print(mystr[len(mystr)-1])
print('marc'[2])
Slicing¶
You can take a subset of a string, we call this 'a slice') using this syntax:
string[beg:end]
the slice starts from offset
beg
(again, counting from 0), and continues through toend-1
Examples:
mystr = 'Python is cool'
print(mystr[0:6])
print(mystr[10:11])
print(mystr[10:10])
print(mystr[10:9])
print('Python is cool'[1:3])
mystr = 'Python is cool'
print(mystr[0:6])
print(mystr[10:11])
print(mystr[10:10])
print(mystr[10:9])
print('Python is cool'[1:3])
ssn = '123-456-7890'
code = input('give me the last four digits of your ssn: ')
if code == ssn[8:]:
print('you are in!!')
else:
print('sorry!')
Default Limits¶
- You can leave out the beg or end part (or both)
- default beg is
0
- default end is
len()-1
Examples:
mystr = 'Python is cool'
print(mystr[0:])
print(mystr[10:])
print(mystr[:14])
print(mystr[:7])
print(mystr[:])
mystr = 'Python is cool'
print(mystr[0:])
print(mystr[10:])
print(mystr[:14])
print(mystr[:7])
print(mystr[:])
Challenge - predict the output from the following print statements:¶
mystr = 'Python is cool'
print(mystr[7])
print(mystr[len(mystr)])
print(mystr[1:15])
print(mystr[1:1])
print(mystr[-5:-1])
print(mystr[-1:-5])
print(mystr[0:])
print(mystr[:-1])
mystr = 'Python is cool'
print(mystr[7])
print(len(mystr))
print(mystr[len(mystr)-1])
print(mystr[1:15])
print(mystr[1:1])
print(mystr[-5:-1])
print(mystr[-1:-5])
print(mystr[0:])
print(mystr[:-1])
The in
Operator¶
in
is a boolean operator which, when used with strings, tests whether a substring exists in another string- the result is a boolean value (True or False)
Examples:
print('a' in 'Marc')
print('arc' in 'Marc')
print('m' in 'Marc')
print('Marc' in 'Marc')
print('' in 'Marc')
print('a' in 'Marc')
print('arc' in 'Marc')
print('m' in 'Marc')
print('Marc' in 'Marc')
print('' in 'Marc')
String Methods¶
- everything in Python is an object
- strings (and other objects) have special functions attached to them which we call "methods"
- string methods are invoked like this:
string.method()
- you can use a variable, like this:
name.capitalize()
or a string literal, like this:'marc'.capitalize()
name = 'Marc Cohen'
print(len(name))
print(name.upper())
print(name.lower())
find()
returns the offset of the first matching substringfind()
returns -1 if no match is found
Example:
mystr = 'bookkeeper'
print(mystr.find('ookkee'))
print(mystr.find('okay'))
mystr = 'bookkeeper'
print(mystr.find('ookkee'))
print(mystr.find('okay'))
print(mystr.find('k'))
print(mystr.find(''))
String Immutability¶
- strings cannot change after they are created
- we call this property “immutability”
- strings are said to be “immutable” (i.e. unchanging)
- string methods which appear to change the string actually return a new string
For example, the upper()
string method:
name = 'Marc'
upper_name = name.upper()
print(f'name={name} still exists, upper_name={upper_name} is a new string')
creates and returns a new string. The original string, the one we called the method on, returns a new string.
name = 'Marc'
upper_name = name.upper()
print(f'name={name} still exists, upper_name={upper_name} is a new string')
String Editing¶
the replace() string method takes these arguments:
- a substring to replace
- a replacement string
- an optional maximum number of occurrences to replace (default is all)
Example: replace every zero with two zeros...
salary = '1000'
new_salary = salary.replace('0', '00')
print(new_salary)
salary = '1000'
new_salary = salary.replace('0', '00')
print(new_salary)
Example: improve your reviews...
feedback = input('How am I doing? ')
better_feedback = feedback.replace('worst', 'best')
print(better_feedback)
feedback = input('How am I doing? ')
improved_feedback = feedback.replace('worst', 'best')
print(improved_feedback)
Loops¶
Stop repeating yourself¶
Let's write a program that prints the first three integers along with the square of each (the number multiplied by itself).
i = 1
print(i, i*i)
i = 2
print(i, i*i)
i = 3
print(i, i*i)
i = 1
print(i, i*i)
i = 2
print(i, i*i)
i = 3
print(i, i*i)
Two problems with this program¶
scalability - what happens to this program if I change the specification from the first three integers to the first 1,000 integers?
You need to type 2,000 more lines of code.
maintainability - what happens to this program if I change the specification to require the cube of each number?
You need to edit every single print statement. After expanding this program to cover the first 1,000 integers, you'd have to change 1,000 lines!
while
Loops¶
- The
while
statement is one way we can express repeatable logic in Python. while
statements define a condition and a sequence of statements in this format...
while condition:
block of indented statements
...
print('done with loop')
- The condition is checked on entry to the loop and after each execution of the loop block.
- Similar to
if
statement except the block is repeatedly executed as long as the condition is true. - When the condition becomes false, the loop is exited, i.e. it stops repeating and control moves to the statement after the loop.
- If the condition is initially false, the loop is never entered, i.e. the loop block is never executed, not even once.
while
Loop Structure¶
In Python, the extent of a while block is defined by indentation (just like if statement blocks)
i = 0
while i < 3:
print(i)
i = i + 1
print(i)
What does the output look like...
- when
i
is initialized to0
? - when
i
is initialized to1
? - when
i
is initialized to2
? - when
i
is initialized to3
? - when
i
is initialized to4
?
i = 0
while i < 3:
print(i)
i = i + 1
print(i)
Loop Counter¶
- variable that controls entry into the loop body
- often keeps track of the number of times the loop is executed
- must be initialized
- must be changed at some point in the loop block to ensure the loop is executed the intended number of times
Assignment Operators¶
i = i + i
can be written like this:i += 1
- More generally,
i = i + x
can be written asi += x
, which adds the value of x to i and assigns the result toi
. - You'll often see this syntax inside loops when we want, for example, to increment a loop counter every time through the loop body.
- We have similar assignment operators available corresponding to the other arithmetic operators:
operator | equivalent |
---|---|
x += y | x = x + y |
x -= y | x = x - y |
x *= y | x = x * y |
x /= y | x = x / y |
x **= y | x = x ** y |
x %= y | x = x % y |
Generating Squares Using a while
Loop¶
i = 1
while i <= 3:
print(i, i*i)
i += 1
- Does this work?
- Is this version an improvement? Why?
i = 1
while i <= 3:
print(i, i*i)
i += 1
Scalability¶
- Can you make this work for the first 1,000 integers?
i = 1
while i <= 1000: # changed 3 to 1000
print(i, i*i)
i += 1
- We scaled this version to 1,000 integers by changing only one line of code!
i = 1
while i <= 1000: # changed 3 to 1000
print(i, i*i)
i += 1
Maintainability¶
- Can you print both the square and the cube (3rd power) for the first 1,000 integers?
i = 1
while i <= 1000:
print(i, i*i, i**3) # added cube to print
i += 1
- The previous version would have required us to edit 1,000 print statements.
- We added support for printing cubes in this version by changing only one line of code!
i = 1
while i <= 1000:
print(i, i**2, i**3) # added cube to print
i += 1
Another Problem¶
- Notice that the loop limit is embedded in the while condition.
- It would be nice if this "configuration data" were separate from the program logic.
- It would also be nice if we could change the limit dynamically without having to change the code, for example, as a function of user input.
A Better Version¶
limit = 10
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
- By using a variable, we can set the limit separately from the loop condition.
limit = 10
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
An Even Better Version¶
limit = int(input('desired number of integers: '))
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
- By using a variable, we can set the limit separately from the loop condition.
- We can also set the limit dynamically, by gathering input from the end user.
limit = int(input('desired number of integers: '))
i = 1
while i <= limit:
print(i, i*i, i**3) # added cube to print
i += 1
Challenge - Find the Bugs¶
There are (at least) four bugs in this program. Can you find them?
start = input('desired starting integer: ")
input('desired ending integer: ')
while i < end:
print(i, i*i, i**3)
start = input('desired starting integer: ")
input('desired ending integer: ')
while i < end:
print(i, i*i, i**3)
Nested while
Loops¶
Just as we had if statements of if statements, we can have while loops of while loops (and so on...)
i = 1
while i <= 3:
j = 1
while j <= 3:
print(i, '/', j, end='\t')
j += 1
print()
i += 1
Result:
1 / 1 1 / 2 1 / 3
2 / 1 2 / 2 2 / 3
3 / 1 3 / 2 3 / 3
i = 1
while i <= 3:
j = 1
while j <= 3:
print(i, '/', j, end='\t')
j += 1
print()
i += 1
Infinite Loops¶
- An infinite loop is a loop that never ends.
- This is usually not by design (!).
Does this loop end?
i = 1
while i <= 3:
print(i, i*i, i**3)
Challenge: how about this one...
i = 5
while i != 0:
print(i, i*i, i**3)
i -= 2 # same as i = i - 2
i = 5
while i != 0:
print(i, i*i, i**3)
i -= 2 # same as i = i - 2
Debugging Infinite Loops¶
- inserting print statements to see what's going on
- we call this "tracing code"
- when problem is fixed, you can comment out or delete your tracing code
Example:
# calculate sum of first n integers
i = 1
n = 10
sum = 0
while i <= n:
i += 1
#print(f"adding i={i} to sum={sum}")
sum += i
print('sum of 1 to', n, '=', sum)
i = 1
while i < 100:
print("I'm running forever")
# calculate sum of first n integers
i = 1
n = 10
sum = 0
while i <= n:
i += 1
#print(f"adding i={i} to sum={sum}")
sum += i
print('sum of 1 to', n, '=', sum)
This progam is fail-safe, right?¶
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment: '))
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
Is there any way this program can go into an infinite loop?
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment: '))
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
Trusting User Input¶
- Getting input from external sources adds flexibility because program behavior can be changed at runtime (without requiring you to change your code).
- The price of that flexibility is that you have to worry about the consequences of receiving bad input.
A More Robust Version¶
import sys # needed for exit() function
start = int(input('starting integer: '))
end = int(input('ending integer: '))
incr = int(input('increment (>0): '))
if (incr <= 0):
sys.exit('incr must be positive')
i = start
while i <= end:
print(i, i*i, i**3, i**4)
i += incr
- you should also verify start and end are numeric
- you can do that with the string method isnumeric()
break
Statement¶
- The break statement exits a loop immediately.
- It's often used to exit a loop under certain circumstances.
- It's Python's way of saying "get me out of here!"
break
Example¶
- Our last version catches bad input but is very unforgiving - it exits as soon as it gets bad input.
- This version gives the user more chances to get it right...
while True:
incr = int(input('increment (>0): '))
if incr > 0:
break
print('bad input, increment must be positive')
print('off we go...')
while True:
value = int(input('positive value (>0): '))
if value > 0:
break
print('bad input, value must be positive')
print('off we go...')
continue
Statement¶
- The continue statement jumps to the top of a loop immediately.
- The loop condition is retested and the next iteration begun (if true) or the loop is exited (if false).
- It's Python's way of saying "let's take it from the top".
continue
Example¶
- Assume we want
start
andend
to be positive. - If the
start
value is bad, don't bother prompting forend
.
start = 0
end = 0
while (start <= 0) or (end <= 0):
if start <= 0:
start = int(input('starting integer (>0): '))
if (start <= 0):
continue
if end <= 0:
end = int(input('ending integer (>0): '))
if end <= 0:
continue
print(‘using start’, start, ‘and end’, end)
start = 0
end = 0
while (start <= 0) or (end <= 0):
if start <= 0:
start = int(input('starting integer (>0): '))
if (start <= 0):
continue
if end <= 0:
end = int(input('ending integer (>0): '))
if end <= 0:
continue
print(f"using start {start} and end {end}")
for
Loops¶
while
loops repeat program logic based on a condition.for
loops repeat program logic based on the contents of a sequence .- We say they "iterate over a sequence".
For example, this program prints the characters in a string, one character per line:
mystr = 'this is a test'
for i in mystr:
print(i)
Result:
t
h
i
s
...
for i in 'Python':
print(i)
Numerical for
Loop¶
- For loops can also be used with a sequence of numbers, similar to while loops.
- For example, this program prints the first three integers and their squares...
for i in (1, 2, 3):
print(i, i*i)
Result:
1 1
2 4
3 9
- Notice a big advantage over while loops: there's no need to separately initialize and increment a loop counter.
- But enumerating the range of numbers is cumbersome.
for i in (1,2,3, 4, 5, 6, 7, 8, 9, 10):
print(i, i*i)
The range()
Function¶
- the Python
range()
function generates a sequence of numbers - prototype: range([start], end, [increment])
- start is optional and defaults to zero
- end is required and terminates the sequence at end-1
- increment dictates the interval between elements
- increment is optional and the defaults to one
range()
returns a special Python type called a "range"
for i in range(0, 10):
print(i, i*i)
range
works great in for
loops¶
Print the print first 20 integers and their squares
for i in range(1, 21):
print(i, i*i)
for i in range(1, 21):
print(i, i*i)
Nested for
Loop¶
- Just as we have seen
if
statements ofif
statements, andwhile
loops ofwhile
loops, we can havefor
loops offor
loops (and so on...)
Example: print a 9-by-9 multiplication table
print('X', end='\t')
for i in range(1, 10):
print(i, end='\t') # column headings
print()
for i in range(1, 10):
print(i, end='\t') # row headings
for j in range(1, 10):
print(i * j, end='\t') # products
# print newline at end of each row
print()
print('X', end='\t')
for i in range(2, 10):
print(i, end='\t') # column headings
print()
for i in range(2, 10):
print(i, end='\t') # row headings
for j in range(2, 10):
print(i * j, end='\t') # products
# print newline at end of each row
print()
Example Program - Image Manipulation¶
from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode
def take_photo(filename=‘photo.jpg’, quality=0.8):
js = Javascript(’’’
async function takePhoto(quality) {
const div = document.createElement(‘div’);
const capture = document.createElement(‘button’);
capture.textContent = ‘Capture’;
div.appendChild(capture);
const video = document.createElement(‘video’);
video.style.display = ‘block’;
const stream = await navigator.mediaDevices.getUserMedia({video: true});
document.body.appendChild(div);
div.appendChild(video);
video.srcObject = stream;
await video.play();
// Resize the output to fit the video element.
google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);
// Wait for Capture to be clicked.
await new Promise((resolve) => capture.onclick = resolve);
const canvas = document.createElement(‘canvas’);
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext(‘2d’).drawImage(video, 0, 0);
stream.getVideoTracks()[0].stop();
div.remove();
return canvas.toDataURL(‘image/jpeg’, quality);
}
‘’’)
display(js)
data = eval_js(‘takePhoto({})’.format(quality))
binary = b64decode(data.split(’,’)[1])
with open(filename, ‘wb’) as f:
f.write(binary)
return filename
from IPython.display import Image
try:
file = take_photo()
print(f"Image saved to {file}")
image = Image(file)
# Show the image which was just taken.
display(image)
except Exception as err:
# Errors will be thrown if the user does not have a webcam or if they do not
# grant the page permission to access it.
print(str(err))
import matplotlib.pyplot as plt
I = plt.imread('photo.jpg')
I = I.sum(axis=-1)
plt.figure()
plt.imshow(I, cmap="gray")
iterations = 10
for x in range(iterations):
for i in range(1,I.shape[0]-1):
for j in range(1,I.shape[1]-1):
I[i,j] = (
I[i-1,j-1] + I[i-1,j] + I[i-1,j+1] +
I[i,j-1] + I[i,j] + I[i,j+1] +
I[i+1,j-1] + I[i+1,j] + I[i+1,j+1]
) / 9.0
plt.figure()
plt.imshow(I, cmap="gray")
Challenges¶
Question 1¶
- Using a while loop, repeatedly prompt the user for an input string, and print the input string.
- Exit the loop when the user enters only the character ‘q’ or an empty string.
Enter a string (‘q’ to quit): this is a test
You entered: this is a test
Enter a string (‘q’ to quit): another test
You entered: another test
Enter a string (‘q’ to quit): q
Exiting...
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
print(f“You entered: {value}")
Question 2¶
Building on the previous program, for each string entered, print the string as entered, followed by an all upper case version and an all lower case version, like this:
Enter a string (‘q’ to quit): I Am The Walrus
Original string: I Am The Walrus
Upper case: I AM THE WALRUS
Lower case: i am the walrus
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
print(f“Original string: {value}")
print(f“Upper case: {value.upper()}")
print(f“Lower case: {value.lower()}")
Question 3¶
Building on the previous program, for each string entered, print whether the entered string is all upper, all lower or mixed case, like this:
Enter a string (‘q’ to quit): this is a test
You entered: this is a test, which is lower case
Enter a string (‘q’ to quit): ANOTHER TEST
You entered: ANOTHER TEST, which is upper case
Enter a string (‘q’ to quit): mIxEd CaSe
You entered: mIxEd CaSe, which is mixed case
Hints:
- Use a for loop to examine the characters in the string.
- Use isupper() and islower() string methods to examine the case of each character in the loop.
- What can you infer by counting the upper and lower case characters in a given string?
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a string (‘q’ to quit): “)
if not value or value == ‘q’:
break
upper_cnt = 0
lower_cnt = 0
length = 0
case = None
for c in value:
if c.isspace():
continue
length += 1
if c.isupper():
upper_cnt += 1
elif c.islower():
lower_cnt += 1
if upper_cnt == length:
case = “upper”
elif lower_cnt == length:
case = “lower”
else:
case = “mixed”
print(f“You entered: {value}, which is {case} case”)
Question 4¶
- The factorial of a number (written x!) is the product of 1, 2, ..., x.
- In other words, it's the result of multiplying 1, 2, ..., all the way up to (and including) the number x itself.
- Prompt the user for a number and print a table of factorials from 1 up to the entered number.
- For example, if the user inputs 5, the output should look something like this (with table headings):
x x!
1 1
2 2
3 6
4 24
5 120
Don't worry if your columns don't line up perfectly.
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a number (‘q’ to quit): “)
if not value or value == ‘q’:
break
value = int(value)
fact = 1
print()
for i in range(1, value + 1):
fact *= i
print(f"{i}\t{fact}")
print()
Question 5¶
- Repeatedly prompt the user for a string (until receiving ‘q’ or an empty string).
- For each string received, use a for loop to create a reversed version of the string.
- As you step through the characters in the string, successively build a reversed version by prepending each char to the reversed version of the string (i.e.
revstr = char + revstr
) - Print the reversed string along with an indication as to whether or not it's a palindrome, written the same forwards and backwards, like 'racecar'. * Don't worry about dealing with upper/lower case issues. You can assume all input is lower case.
- Also don't worry about doing anything special with whitespace characters (assume "race car" is not a palindrome).
Enter a string: hello
hello/olleh - is NOT a palindrome
Enter a string: redivider
redivider/redivider - is a palindrome!
# Add your code here
#@title Double click here to reveal solution
while True:
value = input(“Enter a number (‘q’ to quit): “)
if not value or value == ‘q’:
break
revstr = ”"
for c in value:
revstr = c + revstr
if value == revstr:
print(f"{value}/{revstr} - is a palindrome!")
else:
print(f"{value}/{revstr} - is NOT a palindrome”)
print()
Just For Fun - Some Palindromes¶
Character-wise
- A man, a plan, a canal, Panama!
- I prefer pi
- Dammit, I'm mad!
- Ma is as selfless as I am
- Never odd or even
- Madam, I'm Adam
Word-wise
- King, are you glad you are king?
- Says Mom, "What do you do?" – You do what Mom says.
- You know, I did little for you, for little did I know you.
- Please me by standing by me please.
- Blessed are they that believe they are blessed.
- Escher, drawing hands, drew hands drawing Escher.