1. Computer Password Checker

Let's try and build a simple password checker using the tools we know. The general idea of a password checker is that the user needs to keep trying passwords until they get the correct password. At this point, the user is logged in. What are the key steps we need to build this program? Let's make an outline.

  1. Create a password to be checked
  2. Ask the user for their password guess and keep asking the user for their password until they guess the correct one
  3. Perform whatever action once the user has logged in

There are several key elements that we need to identify. The most important is that there will be some code that we have to repeat potentially if the user keeps guessing incorrectly. Eventually, we use loops to help us. Loops repeat code. Second, we need to keep repeating until some condition is met, namely the user has entered in their password correctly. Formally, we call this a continuation condition. It is the condition that determines whether we need to repeat or to move on to someother code.

Manual Implementation

It turns out that without loops we do not have the means to implement this program. However, we can simulate what we should do by reexecuting certain code cells.

The password

In [1]:
password = "12345"

Keep asking until they get the right password

Here, we need to continually execute the cell until we are no longer prompted to enter a password. We will have to do this manually with the tools we know. This is the moment though where we will need a loop because we want to repeat this block of code until some condition is met (i.e., the password is entered correctly).

In [2]:
# prompt the user
prompt = "Please enter your password: "
suppliedPassword = input(prompt)

# check the password
if password != suppliedPassword:
    print("Please re-execute this cell and enter in the correct password.")
else:
    print("You have logged in correctly!")
Please enter your password: 12345
You have logged in correctly!

Log In Code

This is the code that would execute once the user has guessed the correct password.

In [3]:
print("Welcome")
Welcome

2. Introducing while loops

A while loop is a control flow statement, that repeats a body of code until some condition is met. This is perfect for our password checker! The condition is called a continuation condition. If the continuation condition is True, then we will continue to execute the code body one more time. If the continuation condition is False, then we will not continue and move on to the next statement. The chart below details the control flow of a while loop outlined with the dotted dashes.

Below is our first example of a while loop to implement our password checker.

In [4]:
password = "12345"

# prompt the user
prompt = "Please enter your password: "
suppliedPassword = input(prompt)

# Keep asking the user until password is correct
# continuation condition: password != suppliedPassword
# if continuation condition is True, keep asking for password because they are not equal
# if continuation condition is False, do not execute the body and ask for age because password is correct

while password != suppliedPassword: # continuation condition
    print("Your password is incorrect.  Please re-enter the correct password.")
    suppliedPassword = input(prompt)

# we can only have arrived her is password == suppliedPassword
print("You have logged in successfully!")
Please enter your password: 13456
Your password is incorrect.  Please re-enter the correct password.
Please enter your password: 19089
Your password is incorrect.  Please re-enter the correct password.
Please enter your password: 33
Your password is incorrect.  Please re-enter the correct password.
Please enter your password: 12345
You have logged in successfully!

3. Other examples of while loops

A while loop for counting up

In [5]:
i = 0
while i < 10:
    print(i)
    i += 1
0
1
2
3
4
5
6
7
8
9

Another example of a while loop that depends on user input

In [6]:
prompt = 'Please enter your name (type quit to exit): '
name = input(prompt)

while (name.lower() != 'quit'):
    print('Hi,', name)
    name = input(prompt)
    
print('Goodbye')
Please enter your name (type quit to exit): Anna
Hi, Anna
Please enter your name (type quit to exit): Andy
Hi, Andy
Please enter your name (type quit to exit): Val
Hi, Val
Please enter your name (type quit to exit): Simon
Hi, Simon
Please enter your name (type quit to exit): quit
Goodbye

Example of a while loop that depends on argument value

In [7]:
def printHalves(n):
    while n > 0: 
        print(n)
        n = n//2
        
printHalves(100)
100
50
25
12
6
3
1
In [8]:
printHalves(22)
22
11
5
2
1
In [9]:
printHalves(75)
75
37
18
9
4
2
1

4. Gotcha: Infinite Loops

We sometimes might wrongly write an infinite loop, one that never ends. In these cases, use Ctrl+C to break out of the loop.

In [10]:
def printHalves2(n):
    """Attempts to print positive successive halves of n.
    """
    while n > 0: 
        print(n)
    n = n//2

NOTE: In the Notebook, it might not be possible sometimes to break the loop, even with Kernel|Interrupt. In this case, close the tab and open it again.

In [ ]:
printHalves2(22)

5. Exercise 1: define countDown

Write a function called countDown that takes a number and prints all the numbers from that number down to 1. You can assume that number is an integer.

For example:

countDown(5)
5
4
3
2
1
In [11]:
# Your code here
def countDown(highNum):
    while highNum > 0:
        print(highNum)
        highNum -= 1
In [12]:
countDown(10)
10
9
8
7
6
5
4
3
2
1
In [13]:
countDown(5)
5
4
3
2
1

6. Exercise 2: printOdds

Define a function called printOdds that takes a number and prints all the odd numbers from 1 up to and including that number (if it is odd). For example:

printOdds(10)
1
3
5
7
9
printOdds(13)
1
3
5
7
9
11
13
In [14]:
# Your code here

# Solution 1
def printOdds(x):
    n = 1
    while n <= x:
        if n % 2 == 1:
            print(n)
        n += 1

# Solution 2
def printOdds(x):
    n = 1
    while n <= x:
        print(n)
        n += 2
In [15]:
printOdds(10)
1
3
5
7
9
In [16]:
printOdds(13)
1
3
5
7
9
11
13

7. The Accumulating Pattern

It is common to use a while loop in conjunction with one or more accumulators = variables that accumulate results from processing the elements of a sequence.

All accumulation patterns follow these three steps:

  1. Define an accumulation variable
  2. Iterate to accumulate
  3. Return the accumulation variable

Here is a sumHalves function that takes an nonnegative integer and returns the sum of the values printed by printHalves.

In [17]:
def sumHalves(n):
    """Accumulates the sum of elements that are halves of the preceding numbers.
    """
    sumSoFar = 0
    while n > 0:
        sumSoFar += n 
        n = n//2
    return sumSoFar

sumHalves(22)
Out[17]:
41

8. Printing the iteration table

We can understand the execution of a loop by an iteration table whose columns are state variables that change during the loop and where each row indicates the values of the state variables at a particular point in time.

Here is an iteration table for sumHalves(22):

n sumSoFar
22 0
11 22
5 33
2 38
1 40
0 41

You can get a Python loop to display the rows of an iteration table by adding two print statements:

  1. Add one print statement right before the while loop. This statement is executed once, before the loop execution begins.
  2. Add a second print statement as the last statement in the loop body. This statement is executed for each iteration of the loop.
In [18]:
def sumHalvesTable(n):
    sumSoFar = 0
    print('n:', n, '| sumSoFar:', sumSoFar) # First print is executed once
    while n > 0:
        sumSoFar += n # or sumSoFar = sumSoFar + n
        n = n//2
        print('n:', n, '| sumSoFar:', sumSoFar) # Second print may be executed multiple times (including zero)
    return sumSoFar

sumHalvesTable(22)
n: 22 | sumSoFar: 0
n: 11 | sumSoFar: 22
n: 5 | sumSoFar: 33
n: 2 | sumSoFar: 38
n: 1 | sumSoFar: 40
n: 0 | sumSoFar: 41
Out[18]:
41

Does the sumHalves2 function have the same input/output behavior as sumHalves? Create an iteration table that predicts the output of sumHalves2(22), and test your prediction below:

Here is an iteration table for sumHalves2(22):

n sumSoFar
22 0
11 11
5 16
2 18
1 19
0 19
In [19]:
def sumHalves2(n):
    '''Prints positive successive halves of n'''
    sumSoFar = 0
    while n > 0:
        n = n//2 
        sumSoFar = sumSoFar + n 
    return sumSoFar 

sumHalves2(22)
Out[19]:
19

Moral of the story: the order of updates to state variables can affect how the loop behaves!

9. Exercise 3: define sumBetween

Define a function sumBetween that takes two integer parameters lo and hi and returns the sum of the integers between lo and hi (inclusive). For example:

sumBetween(4, 8)
30

This is because (4 + 5 + 6 + 7 + 8) is 30.

In [20]:
def sumBetween(lo, hi):
    # Your code here
    sumSoFar = 0           # accumulator varible
    while lo <= hi:
        sumSoFar += lo     # update state variables
        lo += 1
    return sumSoFar        # return accumulator
   
sumBetween(4, 8)
Out[20]:
30

Now modify your definition of sumBetween to print an iteration table with the values of the state variables lo, hi, and sumSoFar. It should look something like this:

sumBetweenTable(4, 8)
lo: 4 | hi: 8 | sumSoFar: 0
lo: 5 | hi: 8 | sumSoFar: 4
lo: 6 | hi: 8 | sumSoFar: 9
lo: 7 | hi: 8 | sumSoFar: 15
lo: 8 | hi: 8 | sumSoFar: 22
lo: 9 | hi: 8 | sumSoFar: 30
In [21]:
def sumBetweenTable(lo, hi):
    # Your code here
    sumSoFar = 0           # accumulator varible
    print('lo:', lo, '| hi:', hi, '| sumSoFar:', sumSoFar)
    while lo <= hi:
        sumSoFar += lo     # update state variables
        lo += 1
        print('lo:', lo, '| hi:', hi, '| sumSoFar:', sumSoFar)
    return sumSoFar        # return accumulator
   
sumBetweenTable(4, 8)
lo: 4 | hi: 8 | sumSoFar: 0
lo: 5 | hi: 8 | sumSoFar: 4
lo: 6 | hi: 8 | sumSoFar: 9
lo: 7 | hi: 8 | sumSoFar: 15
lo: 8 | hi: 8 | sumSoFar: 22
lo: 9 | hi: 8 | sumSoFar: 30
Out[21]:
30

10. Another Motivating Example: counting vowels

Assume we are given different words, such as "Wellesley", "Boston", "abracadbra", "osteoarthritis", and so on, and want to count the number of vowels in each word, or to count how many times a particular letter occurs in the word.

How do we do that? First, we'll need our "old friend", isVowel as a predicate that we can use in conditional statements.

Old friend: isVowel

In [22]:
def isVowel(char):
    """Predicate that returns true only when a letter is a vowel."""
    return char.lower() in "aeiou"
In [23]:
def testIsVowel(char):
    """Helper function to test isVowel"""
    print("isVowel('" + char + "')",  "=>",  isVowel(char))
In [24]:
testIsVowel('d')
testIsVowel('D')
testIsVowel('a')
testIsVowel('E')
testIsVowel('i')
testIsVowel('O')
testIsVowel('u')
testIsVowel('y')
isVowel('d') => False
isVowel('D') => False
isVowel('a') => True
isVowel('E') => True
isVowel('i') => True
isVowel('O') => True
isVowel('u') => True
isVowel('y') => False

Use indices to access elements in a string

We can access each character (or element) in a string word by using indices. These are integers from 0 to len(word)-1

In [25]:
word = "Boston"
word[0]
Out[25]:
'B'
In [26]:
len(word)
Out[26]:
6
In [27]:
word[1]
Out[27]:
'o'
In [28]:
word[3]
Out[28]:
't'

Question: Will the following expression work?

In [29]:
word[6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-29-8fed510628cf> in <module>
----> 1 word[6]

IndexError: string index out of range

Question: What about this one, will this work?

In [30]:
word[-1]
Out[30]:
'n'

Question: Can you access the character "s" by using a negative index? Write it below to test:

In [31]:
word[-4]
Out[31]:
's'

Sequential vs. Chained conditionals

How can we count the vowels in a word? We can use isVowel and the indices to test each character and keep track of vowels through a counter variable.

But, how do we write the conditionals to test for each character?

Scenario 1: A series of if statements

Can you predict the result?

In [32]:
word = 'Boston'     
counter = 0
if isVowel(word[0]):
    counter += 1
if isVowel(word[1]):
    counter += 1
if isVowel(word[2]):
    counter += 1
if isVowel(word[3]):
    counter += 1
if isVowel(word[4]):
    counter += 1
if isVowel(word[5]):
    counter += 1
print(counter)        
2

Scenario 2: Chained conditionals

Can you predict the result?

In [33]:
word = 'Boston'     
counter = 0
if isVowel(word[0]):
    counter += 1
elif isVowel(word[1]):
    counter += 1
elif isVowel(word[2]):
    counter += 1
elif isVowel(word[3]):
    counter += 1
elif isVowel(word[4]):
    counter += 1
elif isVowel(word[5]):
    counter += 1
print(counter)
1

Try the code with another word

We always strive to write code that is generic, what will happen when we run it with a new string?

In [34]:
word = 'Lynn'     
counter = 0
if isVowel(word[0]):
    counter += 1
if isVowel(word[1]):
    counter += 1
if isVowel(word[2]):
    counter += 1
if isVowel(word[3]):
    counter += 1
if isVowel(word[4]):
    counter += 1
if isVowel(word[5]):
    counter += 1
print(counter) 
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-34-62d91339842c> in <module>
      9 if isVowel(word[3]):
     10     counter += 1
---> 11 if isVowel(word[4]):
     12     counter += 1
     13 if isVowel(word[5]):

IndexError: string index out of range

Using a While Loop

Remember a while loop is used to repeat a block of code. What code do we want to repeat? We want to repeat the conditional for each index in the string. Here we can use a while loop to start at index 0 and repeat until we reach the last index of the string. We will need two accumulation variables. One to increment the counter and one to increment the index.

In [35]:
word = "Boston"
counter = 0
index = 0

while index < len(word): # continuation condition
    if isVowel(word[index]):
        counter += 1
    index += 1

print(word + ' has ' + str(counter) + ' vowels')
Boston has 2 vowels
In [36]:
word = "Manchester-by-the-Sea"
counter = 0
index = 0

while index < len(word):
    if isVowel(word[index]):
        counter += 1
    index += 1

print(word + ' has ' + str(counter) + ' vowels')
Manchester-by-the-Sea has 6 vowels

Exercise 4: countAllVowels

Abstract over the while loop examples from above to define a function countAllVowels that takes one argument -- a word -- and return the number of vowels in that word.

In [37]:
# Your code here
def countAllVowels(word):    
    counter = 0
    index = 0

    while index < len(word):
        if isVowel(word[index]):
            counter += 1
        index += 1
        
    return counter
In [38]:
def testCountAllVowels(word):
    """Helper function to test countAllVowels"""
    print("countAllVowels('" + word + "')",  "=>",  countAllVowels(word))
In [39]:
testCountAllVowels('Lynn')
testCountAllVowels('Boston')
testCountAllVowels('Wellesley')
testCountAllVowels('Deerfield')
testCountAllVowels('Middleborough')
testCountAllVowels('Manchester-by-the-Sea')
testCountAllVowels('abracadabra')
testCountAllVowels('pneumonoultramicroscopicsilicovolcanoconiosis')
countAllVowels('Lynn') => 0
countAllVowels('Boston') => 2
countAllVowels('Wellesley') => 3
countAllVowels('Deerfield') => 4
countAllVowels('Middleborough') => 5
countAllVowels('Manchester-by-the-Sea') => 6
countAllVowels('abracadabra') => 5
countAllVowels('pneumonoultramicroscopicsilicovolcanoconiosis') => 20

Exercise 5: countChar

Define a function countChar that takes two arguments -- a character and a word -- and returns the number of times that character appears in the word.

In [40]:
# Your code here
def countChar(char, word):
    counter = 0
    index = 0
    
    while index < len(word):
        if word[index] == char:
            counter += 1
        index += 1
        
    return counter
In [41]:
def testCountChar(char, word):
    """Helper function to test countChar"""
    print("countAllVowels('" + char + "', '" + word + "')",  "=>",  countChar(char, word))
In [42]:
testCountChar('i', 'Mississippi')
testCountChar('p', 'Mississippi')
testCountChar('M', 'Mississippi')
testCountChar('m', 'Mississippi')
testCountChar('-', 'Manchester-by-the-Sea')
testCountChar('t', 'To be or not to be')
testCountChar(' ', 'To be or not to be')
countAllVowels('i', 'Mississippi') => 4
countAllVowels('p', 'Mississippi') => 2
countAllVowels('M', 'Mississippi') => 1
countAllVowels('m', 'Mississippi') => 0
countAllVowels('-', 'Manchester-by-the-Sea') => 3
countAllVowels('t', 'To be or not to be') => 2
countAllVowels(' ', 'To be or not to be') => 5