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: "
enteredPassword = input(prompt)

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

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 [3]:
password = "12345"

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

# Keep asking the user until password is correct
# continuation condition: password != enteredPassword
# if continuation condition is True, ask to enter a 
#   new password because enteredPassword is incorrect
# if continuation condition is False, do not execute
#.  the loop body, because enteredPassword is correct

while password != enteredPassword: # continuation condition
    print("That password is incorrect.  Please enter the correct password.")
    enteredPassword = input(prompt)

# we can only have arrived here if password == enteredPassword
print("You have logged in successfully!")
Please enter your password: abcde
That password is incorrect.  Please enter the correct password.
Please enter your password: password1
That password is incorrect.  Please enter the correct password.
Please enter your password: 01234
That password is incorrect.  Please 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 [4]:
i = 0
while i < 10:
    print(i)
    i += 1 # this is a shorthand for 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 [5]:
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): Eni
Hi, Eni
Please enter your name (type quit to exit): Vinitha
Hi, Vinitha
Please enter your name (type quit to exit): Sara
Hi, Sara
Please enter your name (type quit to exit): Sohie
Hi, Sohie
Please enter your name (type quit to exit): Peter
Hi, Peter
Please enter your name (type quit to exit): Lyn
Hi, Lyn
Please enter your name (type quit to exit): quit
Goodbye

Example of a while loop that depends on argument value

In [6]:
def printHalves(n):
    while n > 0: 
        print(n)
        n = n//2
        
printHalves(22)
22
11
5
2
1
In [7]:
printHalves(100)
100
50
25
12
6
3
1
In [8]:
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 or Kernel>Interrupt to break out of the loop.

In [9]:
def printHalves2(n):
    """Attempts to print positive successive halves of n.
    """
    while n > 0: 
        print(n)
    n = n//2 # misindented assignment statement causes an infinite loop!

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 [10]:
printHalves2(22)
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
22
... output manually edited to remove many more lines before Ctrl-C was pressed ...

5. 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 with an intial value (e.g., 0, the empty string).
  2. Iterate to accumulate a result in the accumulation variable.
  3. Return the final result stored in the accumulation variable

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

In [11]:
def sumHalves(n):
    """Accumulates the sum of elements that are halves of the preceding numbers.
    """
    sumSoFar = 0 # initialize accumulation variable
    while n > 0:
        sumSoFar += n # update accumulation variable
        n = n//2
    return sumSoFar # return final value of accumulation variable

sumHalves(22)
Out[11]:
41

6. Iteration tables

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 [12]:
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[12]:
41

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

In [13]:
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[13]:
19

Here is an iteration table for sumHalves2(22):

n sumSoFar
22 0
11 11
5 16
2 18
1 19
0 19

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

7. Exercise: sumDown

Define a function sumDown that takes a single argument -- an integer n --- and returns the sum of all the integers from n down to (and including) 1. E.g.,

  • sumDown(3) should return 6, because 3 + 2 + 1 = 6
  • sumDown(5) should return 15, because 5 + 4 + 3 + 2 + 1 = 15
  • sumDown(0) should return 0, because there are no integers from 0 to 1, and the sum of zero integers is 0.
In [14]:
def sumDown(n): 
    """Assume n is an integer. Return the sum of the integers from n down to (and including) 1."""
    #Your code here
    sumSoFar = 0
    while n > 0:
        sumSoFar = sumSoFar + n
        n = n - 1
    return sumSoFar
In [15]:
def testSumDown(n):
    """Helper function to test countChar"""
    print("sumDown(" + str(n) + ") =>",  sumDown(n))
    
testSumDown(3)   
testSumDown(5)  
testSumDown(10) 
testSumDown(0) 
testSumDown(-1)
sumDown(3) => 6
sumDown(5) => 15
sumDown(10) => 55
sumDown(0) => 0
sumDown(-1) => 0

8. Gotcha: Premature return from a Loop

The level of the indentation of a return statement matters tremendously. In the following function, the return statement is indented so that it is a statement in the while loop body rather than being a statement in the function body. Predict how this difference in indentation affects the behavior of the function:

In [16]:
def sumHalvesBroken(n):
    '''Broken version of sumHalves'''
    sumSoFar = 0
    while n > 0:
        sumSoFar += n # or sumSoFar += n
        n = n//2
        return sumSoFar # wrong indentation!
                        # exits function after first iteration
                        # of loop. Sometimes we want to return 
                        # early from a loop, but not here!

sumHalvesBroken(22)
Out[16]:
22

If a return is enountered within the body of a loop that's within a function. it causes the function to return immediately, effectively terminating the loop early. In sumHalvesBroken, this so-called early return causes the function to return at the end of the first iteration of the loop, just after sumSoFar has been updated to be the value n. So sumHalvesBroken always just returns its argument, which is not the desired behavior. In a few lectures, we'll see examples where early returns are desirable, but in sumHalvesBroken it does the wrong thing!

9. Another Motivating Example: counting characters

Assume we are given different words, such as 'Boston', 'Wellesley', 'abracadbra', 'osteoarthritis', and so on, and want to count the number how many times a particular letter occurs in the word. E.g. "Boston" contains two 'o's, one 's', and zero 'e's.

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 [17]:
word = "Boston"
word[0]
Out[17]:
'B'
In [18]:
len(word)
Out[18]:
6
In [19]:
word[1]
Out[19]:
'o'
In [20]:
word[3]
Out[20]:
't'

Question: Will the following expression work?

In [21]:
word[6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[21], line 1
----> 1 word[6]

IndexError: string index out of range

Question: What about this one, will this work?

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

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

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

Sequential vs. Chained conditionals

How can we count the occurrences of a particular character in a word? We can go through all the indices of letters in the word and use a counter variable keep track of how many letters are equal to the character we're looking for.

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 [24]:
char = 'o'
word = 'Boston'     
charCount = 0
if word[0] == char: 
    charCount += 1
if word[1] == char: 
    charCount += 1
if word[2] == char: 
    charCount += 1
if word[3] == char: 
    charCount += 1
if word[4] == char: 
    charCount += 1
if word[5] == char: 
    charCount += 1
print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
The number of times 'o' appears in 'Boston' is 2

Scenario 2: Chained conditionals

Can you predict the result?

In [25]:
char = 'o'
word = 'Boston'     
charCount = 0
if word[0] == char: 
    charCount += 1
elif word[1] == char: 
    charCount += 1
elif word[2] == char: 
    charCount += 1 
elif word[3] == char: 
    charCount += 1
elif word[4] == char: 
    charCount += 1
elif word[5] == char: 
    charCount += 1
print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
The number of times 'o' appears in 'Boston' is 1

Try the code with other words

We always strive to write code that works in all cases. Will the following examples print the correct result? Why or why not?

In [26]:
char = 'e'
word = 'Wellesley'     
charCount = 0
if word[0] == char: 
    charCount += 1
if word[1] == char: 
    charCount += 1
if word[2] == char: 
    charCount += 1
if word[3] == char: 
    charCount += 1
if word[4] == char: 
    charCount += 1
if word[5] == char: 
    charCount += 1
print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
The number of times 'e' appears in 'Wellesley' is 2
In [27]:
char = 'n'
word = 'Lynn'     
charCount = 0
if word[0] == char: 
    charCount += 1
if word[1] == char: 
    charCount += 1
if word[2] == char: 
    charCount += 1
if word[3] == char: 
    charCount += 1
if word[4] == char: 
    charCount += 1
if word[5] == char: 
    charCount += 1
print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[27], line 12
     10 if word[3] == char: 
     11     charCount += 1
---> 12 if word[4] == char: 
     13     charCount += 1
     14 if word[5] == char: 

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 [28]:
char = 'o'
word = 'Boston'
charCount = 0
index = 0

while index < len(word): # continuation condition
    if word[index] == char:
        charCount += 1
    index += 1
    
print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
The number of times 'o' appears in 'Boston' is 2
In [29]:
char = 'e'
word = 'Wellesley'
charCount = 0
index = 0

while index < len(word):
    if  word[index] == char:
        charCount += 1
    index += 1

print("The number of times '" + char + "' appears in '" + word + "' is", charCount)
The number of times 'e' appears in 'Wellesley' is 3

Enapsulating the while loop in a function

We can now encapsulate the working while loop in a countChar function that takes two arguments -- a character and a word -- and returns the number of times that character appears in the word.

In [30]:
def countChar(char, word):
    
    charCount  = 0 # initialize accumulator variable
    index = 0 # initialize index variable
    
    while index < len(word):
        if word[index] == char:
            charCount += 1 # charCount only when current character is char
        index += 1 # always increment index
        
    return charCount
In [31]:
def testCountChar(char, word):
    """Helper function to test countChar"""
    print("countAllVowels('" + char + "', '" + word + "')",  "=>",  countChar(char, word))
In [32]:
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

10. Exercise: countAlpha

A character is alphabetic if it is a lowercase or uppercase version of one of the 26 letters of the English alphabet --- i.e., a letter between a and z.

The .isalpha() method on a string returns True if all the characters in the string are alphabetic, and False otherwise:

In [33]:
'Cat'.isalpha()
Out[33]:
True
In [34]:
'C4t'.isalpha()
Out[34]:
False

Applying .isalpha() to a single character indicates if that character is alphabetic:

In [35]:
'Z'.isalpha()
Out[35]:
True
In [36]:
'h'.isalpha()
Out[36]:
True
In [37]:
'3'.isalpha()
Out[37]:
False
In [38]:
','.isalpha()
Out[38]:
False
In [39]:
' '.isalpha()
Out[39]:
False

Using .isalpha, define a function countAlpha that takes a single argument -- a string word --- and returns the number of alphabetic characters in the word. For example:

  • countAlpha('Wellesley') => 9
  • countAlpha('CS 111 rocks!') => 7
  • countAlpha('1.23 < 4.5**6') => 0
  • countAlpha('You say, 'Goodbye!' & I say, 'Hello!'') => 22
In [40]:
def countAlpha(word): 
    """Return the number of alphabetic characters in the string word."""
    #Your code here
    alphaCount = 0
    index = 0
    while index < len(word):
        if word[index].isalpha():
            alphaCount += 1
        index += 1
    return alphaCount
In [41]:
def testCountAlpha(word):
    """Helper function to test countAlpha"""
    print("countAlpha('" + word + "') =>",  countAlpha(word))

testCountAlpha("Wellesley")
testCountAlpha("CS 111 rocks!")
testCountAlpha("1.23 < 4.5**6")
testCountAlpha("You say, 'Goodbye!' & I say, 'Hello!'")
countAlpha('Wellesley') => 9
countAlpha('CS 111 rocks!') => 7
countAlpha('1.23 < 4.5**6') => 0
countAlpha('You say, 'Goodbye!' & I say, 'Hello!'') => 22
In [ ]: