6
\$\begingroup\$

This is the task I was given after completing my program. I don't have a clue what procedures and functions are to be honest. Therefore I have no clue on how to break down my program into different elements of it.

#paint calculator

RoomWidth = 0  
RoomHeight = 0
RoomTotal = 0
RoomPaint = 0 

RoomWidth = float(input("Enter the room width, it must be between 1m-25m: "))
RoomHeight = float(input("Enter the room height, it must be between 2m-6m: ")) 
if ((RoomWidth <=25 and RoomWidth >=1) and (RoomHeight <=6 and RoomHeight >=2)): 

    RoomTotal = ((RoomWidth * RoomHeight)*4)

    PaintSelection = str (input("Please choose from a selection of paints: 'Luxury', 'Standard', or 'Economy'."))
    if PaintSelection == "Luxury":
        RoomPaint = RoomTotal * 1.45
    elif PaintSelection == "Standard" :
        RoomPaint = RoomTotal * 1.00
    elif PaintSelection == "Economy" :
        RoomPaint = RoomTotal * 0.45 

    UndercoatAnswer = str (input("Would you like to use an undercoat? Y/N: "))
    if UndercoatAnswer == "Y":
        UndercoatingCost = (RoomTotal * 0.5)
    elif UndercoatAnswer == "N":
        UndercoatingCost = 0

    TotalCost = RoomPaint + UndercoatingCost
    print ("The total cost of painting this room is £",TotalCost,)

else:
    print ("Invalid input")
\$\endgroup\$
3
  • \$\begingroup\$ Welcome to Code Review! You are actually not asking a question here. You say some facts about what you need to do and how you don't know how to do it. I would recommend rethinking your post and asking a question on optimizing/partitioning your code. \$\endgroup\$ Jun 6, 2014 at 15:17
  • 1
    \$\begingroup\$ @DarinDouglass We deal with questions like this all the time in Code Review. \$\endgroup\$ Jun 6, 2014 at 19:05
  • \$\begingroup\$ @200_success The way his original question was phrased, it seemed more like a 'How do I do this?' question, than a 'How can I improve this?' question. \$\endgroup\$ Jun 6, 2014 at 19:17

2 Answers 2

11
\$\begingroup\$

Functions are described in the Python tutorial - they are separate blocks of code which take defined inputs (argument values) and provide defined outputs (return values), and usually have a descriptive comment ("docstring") at the top:

def function(argument):
    """A simple function to double things."""
    output = argument * 2
    return output

As to how you can proceed on refactoring your code to use these functions, I suggest you take the following steps:

Step 1: Abstract repeated code into standalone functions:

def get_float_input(prompt, min_, max_):
    """Take valid float input between min_ and max_ from the user."""
    ...

room_width = get_float_input("Enter the room width, it must be between 1m-25m: ", 1, 25)
room_height = get_float_input("Enter the room height, it must be between 2m-6m: ", 2, 6)

You can use this community wiki to help you write the functionget_float_input.

Step 2: Use dictionaries to simplify multiple elifs, and continue to factor out repeats:

def get_str_input(prompt, choices):
    """Take valid string input from the user from items in choices."""
    ...

prompt = "Please choose from a selection of paints: 'Luxury', 'Standard', or 'Economy'."
paint_factor = {"Luxury": 1.45, "Standard": 1.0, "Economy": 0.45}
room_paint = room_total * paint_factor[get_str_input(prompt, paint_factor)]

prompt = "Would you like to use an undercoat? Y/N: "
undercoats = {"Y": 0.5, "N": 0}
room_undercoat = room_total * undercoats[get_str_input(prompt, undercoats)]

Step 3: Organise whole thing into sensible sub-functions that each do one thing:

def room_size():
    """Calculate the size of a room from user input."""
    width = get_float_input(...)
    height = get_float_input(...)
    return width * height

def paint_cost(size):
    """Calculate the cost of paint for a room."""
    prompt = "..."
    paint_factor = {...}
    return size * paint_factor[get_str_input(prompt, paint_factor)]

def undercoat_cost(size):
    """Calculate the cost of undercoat for a room."""
    prompt = "..."
    undercoats = {...}
    return size * undercoats[get_str_input(prompt, undercoats)]

Step 4: Write one function to bring the rest together:

def main():
    size = room_size()
    total_cost = paint_cost(size) + undercoat_cost(size)
    print("The total cost of painting this room is £{0:.2f}".format(total_cost))

Step 5: Add a call at the bottom of your script to set the whole thing off:

if __name__ == "__main__":
    main()

(see this question if you don't know what that comparison does).

Throughout: Follow the Python style guide, PEP-0008.

\$\endgroup\$
1
\$\begingroup\$

There's no point in initializing these variables to zero. Also, by PEP 8, variables should be named like room_width.

RoomWidth = 0  
RoomHeight = 0
RoomTotal = 0
RoomPaint = 0

Python supports double-ended inequalities, so

if ((RoomWidth <=25 and RoomWidth >=1) and (RoomHeight <=6 and RoomHeight >=2)): 

can be simplified to

if (1 <= room_width <= 25 and 2 <= room_height <= 6):

However, your code would be easier to read if you get the very short error handler out of the way first:

if (not(1 <= room_width <= 25) or not(2 <= room_height <= 6)):
    print("Invalid input")
else:
    # Continue calculating

The user would still likely be puzzled by an "Invalid input" message, though, especially if the width is out of range. We could improve the user experience with better input/validation routines.

def prompt_float(prompt, validator=None):
    while True:
        try:
            val = float(input(prompt))
            if validator is None or validator(val):
                return val
        except ValueError:
            pass
        print("Invalid input")

room_width = prompt_float("Enter the room width, which must be between 1m-25m: ",
    lambda val: 1 <= val <= 25)
room_height = prompt_float("Enter the room height, which must be between 2m-6m: ",
    lambda val: 2 <= val <= 6)

The program would benefit from further generalization of the prompting routine. Basically, your goal is to translate all user input into some kind of number. In the case of the room dimensions, it was just a matter of interpreting a string as a float. However, we could also transform the value using lookups.

By generalizing the prompting routine that was developed above, we get input validation everywhere, for free.

def prompt(prompt, validator=lambda val:val is not None, transform=float):
    while True:
        try:
            val = transform(input(prompt))
            if validator is None or validator(val):
                return val
        except ValueError:
            pass
        print("Invalid input")

room_width = prompt("Enter the room width, which must be between 1m-25m: ",
    validator=lambda val: 1 <= val <= 25)
room_height = prompt("Enter the room height, which must be between 2m-6m: ",
    validator=lambda val: 2 <= val <= 6)

paint_unit_cost = prompt("Please choose from a selection of paints: 'Luxury', ['Standard'], or 'Economy': ",
    transform=lambda val: {
        'luxury': 1.45,
        'standard': 1.00,
        'economy': 0.45,
        '': 1.00,           # "Standard" is the default
    }.get(val.lower()))

undercoat_unit_cost = prompt("Would you like to use an undercoat? Y/N: ",
    transform=lambda val: {'y': 0.5, 'n': 0.0}.get(val.lower()))

cost = 4 * room_width * room_height * (paint_unit_cost + undercoat_unit_cost)
print("The total cost of painting this room is £{0:.2f}".format(cost))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.