Instructions for timeProfilerDeluxe

(produced at 01:48 a.m. UTC on 2021-09-22)

This task is part of ps03 which is due at 23:59 EDT on 2021-09-28.

This is an individual task.

You can submit this task using this link.

Put all of your work for this task into the file timeProfilerDeluxe.py
(you will create this file from scratch)

This task is a chance for you to practice using functions and parameters, including defining your own functions, and using variables in one function as parameters to another function.

Note: although the original task this is building from was a partner task, this task is an individual task.

Overall Goal

The program that you will write for this task achieves almost the same thing as the timeProfiler program from the previous problem set: it asks some questions about how much time the user spends on various tasks, and then prints out a report about how their time per week is divided between those tasks. In this task, there are a few differences in terms of the output:

  1. The prompt for the number of classes that you're taking must accept floating point numbers (e.g., 2.5) as input, not just integers.
  2. The printed output is fancier: in addition to printing the rounded hours-per-week values for each category, it must print a bar of letters to provide a visual indication of the relative values in each category.

In addition to these differences in the input and output of the program, we are requiring you to complete this version of the task using a variety of custom functions of your own design. You will have to think about what functions you'd like to define, and then how to use them to achieve your goal.

You are welcome to re-use the code you wrote for the first version of this task this time around, but you will have to reorganize it to meet the specifications of the new rubric.

Procedure Requirements

You can review the specific requirements for which functions you have to define in the rubric for this task, but the list below gives an overview of the requirements. Ultimately, you must define a main function with no parameters, which, when called, creates the full pattern of input/output behavior shown by the examples.

The requirements that your submission must satisfy include:

  1. All functions must include docstrings.
  2. None of your functions may be defined inside another function.
  3. main itself must not use return, nor may it have any parameters.
  4. It must use at least three distinct custom functions. You are welcome to use more if you wish.
  5. Counting all uses of custom functions, including multiple uses of the same custom function, there must be at least 6 function calls to custom functions in your main function itself.
  6. At least one of your custom functions must call input, and must also use a return statement to return a value.
  7. At least one of your custom functions must have zero parameters.
  8. At least one of your custom functions must have two or more parameters.
  9. At least one of your custom functions must have at least one parameter, and must also use return to return a value.
  10. main, and/or one of the helper functions it uses, must call print in at least one place.
  11. As an extra goal, you should call print in no more than 7 places, including all calls to print that appear in your custom functions.

Note that it's possible for a single custom function to fulfill multiple specific requirements from this list. For example, a custom function which accepted one parameter, called input, and used return to return a value, would satisfy both the requirement that a custom function must call input and use return, and the requirement that a custom function must both have a parameter and use return. In that case, both requirements would count as satisfied by that one custom function.

In addition to the above requirements, there are a two more rules that you must follow while writing your code, which will come up again on many tasks:

  1. Don't waste boxes -- This rule means that you need to eventually make use of every variable you create (because variables are boxes, and creating one without ever using it is a waste). As a corollary, you shouldn't store the result of a non-fruitful function in a variable, since you'll never have a use for that result (which will always be None). "Making use of" a variable simply means using it as part of an expression, including returning it as the result of a function. Note that parameters are a kind of variable! An example of code which violates this rule would be:
    def addOne(num, another):
        plusFive = num + 5
        return num + 1
    
    This code is wasting two boxes: the parameter another is never used inside the function addOne, and the variable plusFive is also never used. The variable num is fine, because it's used both as part of the expression for plusFive, and as part of the return value of addOne.
  2. Don't waste fruit -- This rule means that you should always store the result of a fruitful function in a variable, or use it as part of a larger expression. In the rare case where you need to call a fruitful function but you don't care about the result, you can use the special variable name _ to store the result, which won't count as a violation of the "don't waste boxes" rule even if you never make use of it. An example of code that violates this rule is:
    def getUserAge():
        age = input('How old are you? ')
        int(age)
        return age
    
    In this code, the int function, which is fruitful, is called without storing the result in a variable, or making use of it. In fact, this code is buggy, because although the intent is that a number should be returned from getUserAge, the return value is a string. One corrected version would look like this:
    def getUserAge():
        age = input('How old are you? ')
        return int(age)
    
    Here the result of the fruitful int function is being used as the return value of the custom function, so the "don't waste fruit" rule is being followed (and the function works as intended).

Testing

Every function you write needs to be tested, but one benefit of developing lots of little functions is that you can test them independently to make sure they each work properly before combining them into a larger program. However, for our testing code to run properly, you must only call functions inside of other function definitions. The exception to this is any calls to testing functions from the optimism library, including calls to any functions as part of the definition of a test case using the testCase function. You can (and should) use optimism to test each of your custom functions, although this is not required. You could also test them by hand by running your code and then invoking them in the shell. Unlike with the previous time profiler task, when you run your file, nothing should happen (except perhaps some optimism tests). It's only when main is called that the prompts and printing should occur.

Important: if you do use optimism for testing, be sure to call restoreOutput and restoreInput after your tests are done (usually at the end of the file). If you don't, input and output will remain disabled, and your other functions won't work as expected in the shell! Note that if this isn't working for you, make sure you have at least version 0.13 of optimism.py.

As before, you may wish to use this free difference checker website to carefully inspect your output for any differences compared to the examples.

Approach

You have a lot of freedom in this task to design your own functions, and to really think about how best to use the power of function definition! But that freedom can also be overwhelming. As hints about the kind of functions you could use, we've provided examples of what an askExtraHours function and an indentMessage might do. You're free to define exactly these functions, or similar functions if you wish, or you could complete the task by defining completely different functions. If you're having trouble thinking about how to get started with this task, we suggest the following approach:

  1. First, take your old working code for the time profiler task, which doesn't use functions. You could also use our solution code if your code had some bugs in it (you can access the solution code via the potluck server after the revision period is over).
  2. Next, select all of the code besides any imports and testing code, and indent it (in Thonny, you can select multiple lines of code and hit Tab to indent them all together). Write def main(): before the newly-indented code, and you should now have a main function that works as required for this assignment (except that it doesn't use the required number of helper functions).
  3. Think about which parts of the code are repetitive, and move those parts out of main and into their own functions. Where these functions need access to variables defined in the main function, you'll have to create parameters and have the main function pass those variables in as arguments. Where the main function needs to be aware of some result from the new function you're creating, set that result as its return value, and capture that result in a variable within main. Repeat this process a few times, until you've defined and called enough custom functions to meet the procedure requirements of this task.

An example of extracting code into a new function is as follows: Assume we have defined the following main function (for a different assignment):

def main():
    """
    Asks the user's name and how old they are, and prints a message about
    when they'll turn 100 years old.
    """
    name = input("What is your name? ")
    age = float(input("Hi " + name + ", how old are you? "))
    yearsTo100 = 100 - int(age)
    turns100In = 2021 + yearsTo100
    firstYear = str(turns100In - 1)
    secondYear = str(turns100In)
    print(
        "You will turn 100 years old in either "
      + firstYear + " or " + secondYear + "."
    )

If we want to put the code that asks about age into a new function, we need to use a parameter (because it uses the name variable from the main function) and a return value (because the age that the user enters is used elsewhere in main). So we would change our code to look like this:

def getAge(userName):
    """
    Given someone's name, asks how old they are, and returns their
    response as a floating-point number. Won't work if the user types in
    a string like 'five' that's not made of digits.
    """
    return float(input("Hi " + userName + ", how old are you? "))

def main():
    """
    Asks the user's name and how old they are, and prints a message about
    when they'll turn 100 years old.
    """
    name = input("What is your name? ")
    age = getAge(name)
    yearsTo100 = 100 - int(age)
    turns100In = 2021 + yearsTo100
    firstYear = str(turns100In - 1)
    secondYear = str(turns100In)
    print(
        "You will turn 100 years old in either "
      + firstYear + " or " + secondYear + "."
    )

Note that the name of our parameter doesn't have to match the name of the variable that we will pass in as an argument, although it could have if we wanted it to (i.e., userName could have been just name in this example).

Examples

Valentina example

Results for a fictional student named Valentina. Note that the input and output here is a result of calling the main function, not of running the file.

In []:
main()
Prints
What is your name? Valentina How many hours do you work each week? 0 How many hours per day do you sleep on average? 8 How many classes are you taking this semester? 5 For one class, what is the average time spent in the classroom per week? 2.5 What shall we call the 'everything outside of class' category? fun How many hours per week do you spend on 'fun'? 15 Weekly time profile for Valentina: 0.0 work hours: 56.0 sleep hours: SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 37.5 class hours: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 15.0 fun hours: XXXXXXXXXXXXXXX 59.5 free hours: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Note: Notice how the colons are always aligned in the examples above, and where they are depends on the length of the longest category message, which in turn depends on the numbers involved and the name of the 'extra' category. You will have to figure out how to use custom functions to achieve this output. Also note how each of the output lines for a time category, including the longest one, has at least one space in front (i.e., the '56' and '37' above line up with the 'ee' of 'Weekly', not the 'We'). Also notice how the number of letters in each bar is the result of rounding the hours for that bar to an integer.

Emma example

Results for a fictional student named Emma.

In []:
main()
Prints
What is your name? Emma How many hours do you work each week? 8.6 How many hours per day do you sleep on average? 6.75 How many classes are you taking this semester? 4 For one class, what is the average time spent in the classroom per week? 3.67 What shall we call the 'everything outside of class' category? extracurricular How many hours per week do you spend on 'extracurricular'? 22.5 Weekly time profile for Emma: 8.6 work hours: WWWWWWWWW 47.25 sleep hours: SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 44.04 class hours: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 22.5 extracurricular hours: XXXXXXXXXXXXXXXXXXXXXX 45.61 free hours: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Sofia example

Results for a fictional student named Sofia.

In []:
main()
Prints
What is your name? Sofia How many hours do you work each week? 5 How many hours per day do you sleep on average? 6.55 How many classes are you taking this semester? 4 For one class, what is the average time spent in the classroom per week? 3.2 What shall we call the 'everything outside of class' category? organizing How many hours per week do you spend on 'organizing'? 24 Weekly time profile for Sofia: 5.0 work hours: WWWWW 45.85 sleep hours: SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 38.4 class hours: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 24.0 organizing hours: XXXXXXXXXXXXXXXXXXXXXXXX 54.75 free hours: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

Hint #1

This shows how one of the functions that our solution uses works. It's called askExtraHours and it uses one parameter: the name of the extra category. It uses the input function to ask the user how many hours they spent on the activity whose name was specified as an argument, and it converts the result to a floating-point number before returning it. You don't have to define a function like this, but if you did, it would satisfy some of the custom function goals.

In []:
askExtraHours('games')
Prints
How many hours per week do you spend on 'games'? 54.321
Out[]:
54.321
In []:
askExtraHours('zoning out')
Prints
How many hours per week do you spend on 'zoning out'? 54.321
Out[]:
54.321

Hint #2

This shows how one of the functions that our solution uses works. It's called indentMessage and it uses two parameters: a message to indent, and a desired length. It adds spaces at the left side of the message to meet the desired length, but if the message is already long enough, it doesn't add anything. It returns the indented message string as its result. You don't have to define a function like this, but if you did, it would satisfy some of the custom function goals.

In []:
indentMessage('12.0 work hours', 20)
Out[]:
' 12.0 work hours'
In []:
indentMessage('37.57 sleep hours', 20)
Out[]:
' 37.57 sleep hours'
In []:
indentMessage('short', 3)
Out[]:
'short'

Test Case

This example shows how you could define a test case for your main function using optimism. Feel free to copy it into your file and use it as a template for more tests. Note that when testing your custom functions, if you want to test their return value instead of what they print, you will use expectResult instead of expectOutputContains.

In []:
from optimism import * captureOutput() provideInput(''' Valentina 0 8 5 2.5 fun 15 ''') testCase(main()) expectOutputContains("Weekly time profile for Valentina:") expectOutputContains(" 0.0 work hours: ") expectOutputContains( " 56.0 sleep hours:" + " SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS" ) expectOutputContains( " 37.5 class hours: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" ) expectOutputContains(" 15.0 fun hours: XXXXXXXXXXXXXXX") expectOutputContains( " 59.5 free hours:" + " FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" ) restoreOutput() restoreInput()
Logs
✓ timeProfilerDeluxe.py:219 ✓ timeProfilerDeluxe.py:220 ✓ timeProfilerDeluxe.py:222 ✓ timeProfilerDeluxe.py:226 ✓ timeProfilerDeluxe.py:228 ✓ timeProfilerDeluxe.py:230

Note that the use of restoreOutput and restoreInput is important here: without them, interactive testing won't work.

Rubric

 
unknown Style Requirements
How your code is written.
 
unknown Core goals
Complete all core goals for core credit. Get partial credit for completing at least half, and more partial credit for completing at least 90%.
 
unknown All functions are documented
Each function you define must include a non-empty documentation string as the very first thing in the function.
 
unknown Procedure Requirements
What code you use to solve the problem.
 
unknown Core goals
Complete all core goals for core credit. Get partial credit for completing at least half, and more partial credit for completing at least 90%.
 
unknown Do not define functions inside of other functions
None of your function definitions may be placed inside of other function definitions.
 
unknown Define main
Use def to define main
 
unknown Call at least 3 distinct custom functions that you design.
Within the definition of main, your code must call at least 3 different custom functions defined elsewhere in the file. There are a few other requirements for these custom functions, but you can use a single custom function to fulfil multiple of the other requirements listed, as long as you have at least 3 different custom functions in total and you call each of them within main. You are welcome to use more than 3 custom functions if you wish.
 
unknown Call a custom function that uses input and return
Within the definition of main, call -any function- in at least once place.
 
unknown Call input
Within the call to a custom function that uses input and return within the definition of main, call input in at least once place.
 
unknown Use a return statement
Within the call to a custom function that uses input and return within the definition of main, use return _ in at least once place.
 
unknown Call a custom function with zero parameters
Within the definition of main, call -any function-(<at most 0 arguments>) in at least once place.
 
unknown Call a custom function with at least two parameters
Within the definition of main, call -any function-(<at least 2 arguments>) in at least once place.
 
unknown Call a custom function with at least one parameter that uses return
Within the definition of main, call -any function-(<at least 1 arguments>) in at least once place.
 
unknown Use a return statement
Within the call to a custom function with at least one parameter that uses return within the definition of main, use return _ in at least once place.
 
unknown Call print in at least one place.
Within the definition of main, you must call print in at least one place, either in main or in one of your custom functions.
 
unknown Do not call any non-testing functions outside of function definitions.
All of your function calls must only appear inside the definitions of other functions. The exception is that you're allowed to call functions from the 'optimism' module anywhere. You can use the shell for interactive testing.
 
unknown Extra goals
Complete all extra goals in addition to the core goals for a perfect score.
 
unknown Do not ignore the results of any fruitful function calls
According to the "Don't waste fruit" principle, every place you call a fruitful function (built-in or custom) you must store the result in a variable, or that function call must be part of a larger expression that uses its return value.
 
unknown Do not create any variables that you never make use of
According to the "Don't waste boxes" principle, every time you create a variable (using = or by defining a parameter for a function) you must also later use that variable as part of another expression. If you need to create a variable that you won't use, it must have the name _, but you should only do this if absolutely necessary.
 
unknown Define main
Use def to define main
 
unknown Call custom functions in at least 6 places.
Within the definition of main, your code must call your custom functions in at least 6 different places. You can fulfill this goal with any combination of your custom functions, as long as the total number of calls is 6 or more.
 
unknown Call print in no more than 7 places.
Within the definition of main, you must call print in at most seven places, including all calls in your custom functions. Note that there are exactly 7 lines of output that you need to print, including the blank line after the inputs.
 
unknown Do not use a return statement
Within the definition of main, do not use return _.
 
unknown Behavior Requirements
What your code does from the user's perspective.
 
unknown Core goals
Complete all core goals for core credit. Get partial credit for completing at least half, and more partial credit for completing at least 90%.
 
unknown Results of running your main function are approximately correct (Valentina input)
We will test what is printed by your code, given the example input for 'Valentina'.
 
unknown Reported time values and bars are correct (Valentina input)
We will check that the output lines which start with numbers closely match the correct output, for the 'Valentina' example.
 
unknown Results of running your main function are approximately correct (Emma input)
We will test what is printed by your code, given the example input for 'Emma'.
 
unknown Reported time values and bars are correct (Emma input)
We will check that the output lines which start with numbers closely match the correct output, for the 'Emma' example.
 
unknown Results of running your main function are approximately correct (Sofia input)
We will test what is printed by your code, given the example input for 'Sofia'.
 
unknown Reported time values and bars are correct (Sofia input)
We will check that the output lines which start with numbers closely match the correct output, for the 'Sofia' example.
 
unknown Extra goals
Complete all extra goals in addition to the core goals for a perfect score.
 
unknown Results of running your main function are correct (extra input #1)
We will test what is printed by your code, given some new inputs (extra test #1).
 
unknown Results of running your main function are correct (extra input #2)
We will test what is printed by your code, given some new inputs (extra test #2).