Keep asking: numbers

Multiple choice

What's a float in Python?

Saving
A

A whole number with no decimal part, like 2 and 44, but not 3.14.

B

A number with a decimal part, like 3.14 or 2.71. 5.0 is not a float.

C

A number that might have a decimal part, but doesn't have to. 3.14, 2.71, and 5.0 are all floats.

Not graded. So why do it?

Multiple choice

If you want to use this…

  • sys.exit()

… what should you add to your code?

Saving
A

import python.sys.exit

B

Nothing. It's a built-in function.

C

import sys

D

import python.sys

Not graded. So why do it?

In the last lesson, you learned how to keep asking the user for a text input until they gave a valid value. Now, let's ask them to type in a float, and keep asking until we get a good one.

I/O

Here's a sample of the I/O we want.

  1. Meal cost? don't know
  2. Sorry, you must enter a number.
  3. Meal cost? sixty dollars
  4. Sorry, you must enter a number.
  5. Meal cost? -10
  6. Sorry, please enter a number more than zero, and less than 1,000,000.
  7. Meal cost? 1234567
  8. Sorry, please enter a number more than zero, and less than 1,000,000.
  9. Meal cost? 60
  10. Service level (g=great, o=ok, s=sucked)? o
  11. -----------------
  12. Meal cost: 60.0
  13. Tip rate: 15 %
  14. Tip: 9.0
  15. Total: 69.0

There are two types of errors users can make:

  • Entering nonnumeric data. Lines 2 and 4 show what error message we want.
  • Entering a number that's out of range, that is, too small or too big. See lines 6 and 8 show you the error message.

Asking one time

Here's the code we used earlier to validate that user input is numeric. The nonlooping version.

  1. # Get the cost of the meal.
  2. user_input = input('Meal cost? ')
  3. # Test whether input was numeric.
  4. try:
  5.     meal_cost = float(user_input)
  6. except ValueError:
  7.     print('Sorry, you must enter a number.')
  8.     sys.exit()
  9. # Test range.
  10. if meal_cost <= 0 or meal_cost > 1000000:
  11.     print('Sorry, please enter a number more than zero, and less than 1,000,000.')
  12.     sys.exit()

Line 2 gets data from the user.

Reflect

What data type will user_input be?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
Georgina

It'll be a string. input() always returns a string.

Right.

Lines 5...

  • meal_cost = float(user_input)

...tries to convert what the user typed (which is in user_input) into a float. It might fail, so we wrap line 5 in a try/except block.

  • try:
  •     meal_cost = float(user_input)
  • except ValueError:
  •     print('Sorry, you must enter a number.')
  •     sys.exit()

If there's a ValueError - which is what we'll get if user_input isn't numeric - the code in the except block will run. It will output a message, and stop the program.

Checking for integers

Here's the code again:

  • try:
  •     meal_cost = float(user_input)
  • except ValueError:
  •     print('Sorry, you must enter a number.')
  •     sys.exit()

This checks whether the user entered a float value, like 22.44. In an earlier lesson, we talked about integers. Integers are whole numbers. So 32 is an integer, but 3.2 is not.

This is from the earlier lesson:

Integers are primarily used for counting things. For example, if you're counting people, you have 23 people or 24 people, not 23.4 people. I wouldn't want 0.4 of a person at a party. Eeewwww.

Say we wanted users to tell us how many people were invited to a party. Here's some code:

  • try:
  •     people = int(user_input)
  • except ValueError:
  •     print('Sorry, you must enter a whole number.')
  •     sys.exit()

Only one thing has changed: float has become int. Python has more data types, and strings can be converted into most of them. We'll only about the ones we need for this course.

Going loopy

We'll wrap the code in a while loop, just as we did for the string validation loop.

With string validation, there was one kind of error: not one of the valid options. Now, there are two kinds of errors people can make: nonnumeric and out-of-range.

However, in general, there can be any number of kinds of errors. For example, suppose you have an order form on a website. Customers enter the number of items they want to buy.

Order form

Vegemite is yummy! Well, to me.

Possible errors include:

  • User types nonnumeric data.
  • User types an out-of-range value, like -3 or 191,299,133.
  • User types a number, but we don't have that many in stock.
Ethan
Ethan

Isn't the last one a range error?

Kinda, but the top value varies depending on inventory. Today, you can order up to, say, 58. Tomorrow, up to 42, because we sold some.

Business policies might mean we add other errors. For example, maybe new customers can only order up to 5 jars. So trying to order 6 might or might not be an error, depending on who's placing the order.

The point is, there can be any number of kinds of errors.

It'd be nice if we had a way to write a validation loop we could easily add and remove error tests.

Multiple choice

What's a constant?

Saving
A

A variable that never changes.
For example, if the variable legs is set to 4,
it can never change in the rest of the code.

B

A value like 23, -9.01, or 'Doggos!'.
23 is always 23.

C

An if or while statement that always runs,
no matter what.

D

A letter that's not a vowel, like b or z.

Not graded. So why do it?

A better way

Here's a way to do it that works well. Let's start with a pseudocode version.

We need a variable to keep track of whether the input is OK or not. Let's call it is_input_ok.

(Remember, this is not real code yet.)

  1. is_input_ok = No
  2. While is_input_ok is No:
  3.     is_input_ok = Yes
  4.     Get input
  5.     if data is bad for some reason:
  6.         is_input_ok = No
  7.     if data is bad for some other reason:
  8.         is_input_ok = No
  9.     As many tests as we want

Each time through the loop, we start by assuming the input is OK (line 3). Then we get the input from the user (line 4). Then we have a buncha tests. In each one, if there's a problem, we remember the data is bad.

By the time we get to the end of the loop's code block (after line 9 is done), we have only two possibilities for is_input_ok:

  • If none of the tests shows the data is bad, is_input_ok will still be Yes. It's set to Yes in line 3, and never changes to No.
  • If any of the tests show there's a problem, is_input_ok is No.

Then loop back to the while (line 2). If is_input_ok is No, run the loop again. Keep running it while is_input_ok is No.

Reflect

Why does line 1 set is_input_ok to No?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
Adela

So the while loop runs at least once.

Aye, that's it.

Let's look at a more Pythony version.

  1. is_input_ok = False
  2. while not is_input_ok:
  3.     is_input_ok = True
  4.     Get some input
  5.     if some test:
  6.         Show error message
  7.         is_input_ok = False
  8.     if is_input_ok:
  9.         if some other test:
  10.             Show error message
  11.             is_input_ok = False
  12.     if is_input_ok:
  13.         if yet another test:
  14.             Show error message
  15.             is_input_ok = False
  16.     if is_input_ok:
  17.         if yet yet another test:
  18.             Show error message
  19.             is_input_ok = False
  20.     As many tests as we like

is_input_ok is a boolean variable. We have strings, floats, integers, and now we have another data type: boolean. Boolean variables can only be True or False. They're mainly used to remember whether something happened or not, like whether a program found an error in user input.

Let's break it down. Line 1...

  • is_input_ok = False

... creates the variable and sets it to False. False is a boolean constant. There are only two boolean constants, since a boolean can only be true or false. Python wants the first letter of true and false to be uppercase. I don't know why.

Line 2...

  • while not is_input_ok:

... keeps running its code block until is_input_ok is True, that is, is not False. In English, it would be: while the input isn't OK.

We're about to do the first pass through this code:

  1. is_input_ok = False
  2. while not is_input_ok:
  3.     is_input_ok = True
  4.     Get some input
  5.     if some test:
  6.         Show error message
  7.         is_input_ok = False
  8.     if is_input_ok:
  9.         if some other test:
  10.             Show error message
  11.             is_input_ok = False
  12.     if is_input_ok:
  13.         if yet another test:
  14.             Show error message
  15.             is_input_ok = False
  16.     if is_input_ok:
  17.         if yet yet another test:
  18.             Show error message
  19.             is_input_ok = False
  20.     As many tests as we like

Start by assuming the input we're about to get is OK (line 3). Get the input (line 4). Then a buncha tests, as many as we like. If any fail, do is_input_ok = False.

When the code block is done, go back up to the while (line 2):

  • while not is_input_ok:

If is_input_ok is False, ask the user again.

Georgina
Georgina

The last lesson's loop, the string validation one. Could we have written it with a boolean?

Aye. I like to explain things in small steps, though. There was enough new stuff in that lesson without adding booleans as well.

Here's the code from then, and a new version.

  • # Initialize service_level
  • service_level = ''
  • # Is the service level valid?
  • while service_level != 'g' and service_level != 'o' and service_level != 's':
  •     # Don't have a valid service level yet
  •     # Ask user to input the service level
  •     service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  •     # Normalize input
  •     service_level = service_level.strip().lower()
  •     # Is the service level valid?
  •     if service_level != 'g' and service_level != 'o' and service_level != 's:
  •         # Show error message
  •         print('Please enter G, O, or S.')
  • is_input_ok = False
  • while not is_input_ok:
  •     # Start off assuming data is OK.
  •     is_input_ok = True
  •     service_level = input('Service level (g=great, o=ok, s=sucked)? ')
  •     # Normalize service level
  •     service_level = service_level.strip().lower()
  •     # Validate service level
  •     if service_level != 'g' and service_level != 'o' and service_level != 's':
  •         is_input_ok = False
  •         print('Please enter G, O, or S.')
Georgina
Georgina

This stuff is so cool!

Ethan
Ethan

Yeah, it is. So logical. Lotsa deets, though.

Indeed. Using patterns, comments, and meaningful variable names help you with the deets. Managing your brain's load is important for programmers. The real name for "brain load" is cognitive load.

The next part of the course is about functions, another way of reducing cognitive load.

The flag pattern

is_input_ok is a flag variable. The word "flag" is used in various ways in programming. To keep it simple, we'll use it one way.

Pattern

Flag

Use a variable as a flag. Set the flag if any of a number of things happens. After, check the flag to see if any of those things happened.

When you start a new project, you can use the pattern catalog to remind yourself of useful chunks of code.

Georgina
Georgina

I have an idea, but don't know whether it's right.

You've told us to make programs easy to think about. They have fewer bugs, and are easier to change.

Right. That's part of software cost and quality control.

Georgina
Georgina

So, look at this flaggy pseudocode:

  1. is_data_ok = False
  2. while not is_data_ok:
  3.     is_data_ok= True
  4.     Get some data
  5.     if something is wrong with the data
  6.         Do something
  7.         is_data_ok = False
  8.     if is_data_ok:
  9.         if something else is wrong with the data
  10.             Do something else
  11.             is_data_ok = False
  12.     if is_data_ok:
  13.         if something else is wrong with the data
  14.             Do something else
  15.             is_data_ok = False
  16.     if is_data_ok:
  17.         if something else is wrong with the data
  18.             Do something else
  19.             is_data_ok = False
  20.     ...

To add another test, I don't need to think about the entire program. As long as I use is_data_ok correctly, I can just work on the test. Easier on my brain, so less chance of a bug.

Aye! That's exactly right! Breaking code into independent chunks makes life easier. The flag pattern helps with that.

Back to meal cost

OK, let's use this stuff to make a meal cost validation loop for the tip program. Remember, the pattern is:

  • is_input_ok = No
  • While is_input_ok is No:
  •     is_input_ok = Yes
  •     Get input
  •     if data is bad for some reason:
  •         is_input_ok = No
  •     if data is bad for some other reason:
  •         is_input_ok = No
  •     As many tests as we want

Let's change it to Python.

Who wants to start?

Ethan
Ethan

I can. Here are the first few lines.

Pseudocode Python

  • is_input_ok = No
  • While is_input_ok is No:
  •     is_input_ok = Yes
  •     Get input
  •     if data is bad for some reason:
  •         is_input_ok = No
  •     if data is bad for some other reason:
  •         is_input_ok = No
  •     As many tests as we want

  • # Get the cost of the meal.
  • is_input_ok = False
  • while not is_input_ok:
  •     # Start off assuming data is OK.
  •     is_input_ok = True
  •     # Get input from the user.
  •     user_input = input('Meal cost? ')

Good! Now, we want to add two tests.

Reflect

What two things do we need to check?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Ray
Ray

Whether the input is a number.

Whether the number is too small or too big.

Right.

I'll add some comments to remind us.

Pseudocode Python

  • is_input_ok = No
  • While is_input_ok is No:
  •     is_input_ok = Yes
  •     Get input
  •     if data is bad for some reason:
  •         is_input_ok = No
  •     if data is bad for some other reason:
  •         is_input_ok = No
  •     As many tests as we want

  • # Get the cost of the meal.
  • is_input_ok = False
  • while not is_input_ok:
  •     # Start off assuming data is OK.
  •     is_input_ok = True
  •     # Get input from the user.
  •     user_input = input('Meal cost? ')
  •     # Is the input numeric?
  •  
  •     # Check the range.
Adela
Adela

I can do the first check.

Reflect

What's the code for the numeric test?

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Adela
Adela

I'd do it like this...

  •     # Is the input numeric?
  •     try:
  •         meal_cost = float(user_input)
  •     except ValueError:
  •         print('Sorry, you must enter a number.')
  •         is_input_ok = False

Note

Adela could think just about the numeric test code, because of the regular structure of the pattern. Easier than trying to keep the entire program in her mind all at once. Easy is good.

Who wants to do the range check?

Reflect

Write the code for the range check.

If you were logged in as a student, the lesson would pause here, and you'd be asked to type in a response. If you want to try that out, ask for an account on this site.
Georgina
Georgina

Ooo! Me!

  •     # Test range.
  •     if is_input_ok:
  •         if meal_cost <= 0 or meal_cost > 1000000:
  •             print('Sorry, please enter a number more than zero, and less than 1,000,000.')
  •             is_input_ok = False

Good!

Note

Georgina only has to know about is_input_ok and meal_cost. The rest of the program doesn't matter while she writes this code fragment. Easier that way. Easy is good.

Let's put it all together.

Pseudocode Python

  • is_input_ok = No
  • While is_input_ok is No:
  •     is_input_ok = Yes
  •     Get input
  •     if data is bad for some reason:
  •         is_input_ok = No
  •     if data is bad for some other reason:
  •         is_input_ok = No
  •     As many tests as we want

  • # Get the cost of the meal.
  • is_input_ok = False
  • while not is_input_ok:
  •     # Start off assuming data is OK.
  •     is_input_ok = True
  •     # Get input from the user.
  •     user_input = input('Meal cost? ')
  •     # Is the input numeric?
  •     try:
  •         meal_cost = float(user_input)
  •     except ValueError:
  •         print('Sorry, you must enter a number.')
  •         is_input_ok = False
  •     # Check the range.
  •     if is_input_ok:
  •         if meal_cost <= 0 or meal_cost > 1000000:
  •             print('Sorry, please enter a number more than zero, and less than 1,000,000.')
  •             is_input_ok = False

Done!

Patternism

A new pattern.

Pattern

Numeric validation loop

Loop while a data-OK flag is false:

    Set a data-OK flag to true.

    Check whether the data is numeric. If not, set the data-OK flag to false.

    If the data-OK flag is still true, do a range test. If the data is out of range, set the data-OK flag to false.

    Repeat for as many tests as you need.

Summary

  • A while loop can keep asking the user for input until it gets something valid.
  • There are at least two kinds of errors people can make: nonnumeric and out-of-range.
  • Use a variable as a flag. Set the flag if any of a number of things happens. After, check the flag to see if any of those things happened.

Exercise

Exercise

Economic order quantity

Your company buys Stuffs from wholesalers, and sells it retail. Work out the optimal order quantity. That is, how many Stuffs you should order each time to minimize cost.

Here's some I/O:

  • Sales? 5000
  • Ordering cost? 90
  • Carrying cost? 6
  • Are there Smurfs (Y/N)? n
  •  
  • EoQ: 387

More:

  • Sales? 5000
  • Ordering cost? 90
  • Carrying cost? 6
  • Are there Smurfs (Y/N)? y
  •  
  • EoQ: 388

One more:

  • Sales? some
  • Sorry, you must enter a whole number.
  • Sales? 5000
  • Ordering cost? -3
  • Sorry, ordering cost cannot be less than zero.
  • Ordering cost? 90
  • Carrying cost? something
  • Sorry, you must enter a number.
  • Carrying cost? 6
  • Are there Smurfs (Y/N)? Don't know
  • Please enter Y or N.
  • Are there Smurfs (Y/N)? y
  •  
  • EoQ: 388

Validation rules:

  • Sales (units per year). Must be an integer that is one or greater.
  • Ordering cost ($). The cost of placing one order. Must be a float that is not negative.
  • Carrying cost ($/unit/year). Cost of carrying a unit per year. Storage, insurance, like that. Must be a float that is more than zero.
  • Whether Smurfs are present. After trimming spaces, should be Y or N, though case doesn't matter.

When the data is OK, compute the optimal order quantity, and show it. The formula from Wikipedia is:

Formula

Where:

Variables

If there are Smurfs, add one to the order quantity. They always steal just one.

Upload a zip of your project folder. The usual coding standards apply.