DEV Community

Cover image for Solving the Problem Sets of CS50's Introduction to Programming with Python — One at a Time: Problem Set 3
Eda
Eda

Posted on • Updated on • Originally published at rivea0.github.io

Solving the Problem Sets of CS50's Introduction to Programming with Python — One at a Time: Problem Set 3

Read the original blog post here.


On this week's problem set on Exceptions, we are given four problems this time, instead of five. The problem explanations are quite comprehensive; which I assume you read beforehand. Reminding the disclaimer that these blog posts are only for a walkthrough of the problems, let's tackle them one by one.

You can also read the previous posts on Problem Set 0, Problem Set 1, and Problem Set 2.

Fuel Gauge

This is a problem where we need to take a user input which we assume to be formatted as X/Y, and display how much fuel left in the tank as a percentage. We are mostly dealing with integer division and exception handling here. Considering we have been getting input from the user for the past problems already, we know how to handle the input accordingly, say, for splitting a string with the format X/Y. Remember that when we split a sentence into words, the default split character is the space character. So if we want to split a string like 1+2 into two numbers, using '1+2'.split('+') will give a list containing 1, and 2.

Before we do any error checking though, we need to get the result as a percentage. And, before getting the result, it might be a better idea to check if the value of x is not larger than y; then calculating the result accordingly. You must be mostly familiar with converting decimals to percentages from elementary school math. In this case, just multiplying the result of the given fraction with 100 is enough. Then, as the problem explanation says, if the overall result is more than 99, we print F to indicate the fuel tank is full; if it is less than 1, we print E to indicate it is empty. Otherwise, we print the percentage, which is done easier with an f-string. We also need to print it with zero decimal places — remember putting something like :.1f after a float type formats it to have 1 place after the decimal, but in this case, we want zero decimal places.

The main thing to consider is handling the exceptions, of course, wrapping the code in a try...except block. As we also need to keep asking the user if there is a ValueError, or a ZeroDivisionError, what we need to do is similar to the example given in this week's lecture — wrapping the exception handling inside an infinite loop which we can break out of with returning the formatted percentage result. And, that is really all there is to it, let's look at the next one.

Felipe's Taqueria

Here, we are already given the menu entrées as a dictionary, the only thing for us to do is to get the user input for an item in the menu, and accumulate the total result of each item that they put in. Of course, we also need to print the total result. Similar to the examples in this week's lecture and the Fuel Gauge problem, we can use an infinite loop to continue getting input from the user. We also need to convert the input into titlecase for it to match the keys in our given dictionary as well. In case of an invalid item which will result in a KeyError, say Burger, we can ignore it (simply, pass) and continue asking the user for an item. If the user hits control-d (a common way to stop the inputs) which will result in an EOFError (end-of-file condition), it is time to stop the program, we can do that by returning from the function after printing a newline.
Well, that was easy. On to the next problem.

Grocery List

This problem is also similar to the ones we did before, and it is very easy to implement if you like using dictionaries. Just like the previous two problems, we need to keep getting input — which we did before by putting the try...except block inside an infinite loop, and returning (if we use it inside a function) at the right time to break out of it. We can use a dictionary to add the items and increment each of the item's value if it is already in the dictionary. Actually, let's see something similar in action. Let's say we want to get the names of spells that Harry Potter has cast in a day, as well as how many times they are used. Perhaps the most intuitive way to do it is similar to this one:

spells = {}
while True:
    try:
        spell = input()
        if spell in spells:
            spells[spell] += 1
        else:
            spells[spell] = 0
    except EOFError:
        break
Enter fullscreen mode Exit fullscreen mode

So, if our input is something like this one:

Accio
Accio
Lumos
Expelliarmus
Expelliarmus
Expelliarmus
Enter fullscreen mode Exit fullscreen mode

Printing each value and key in our spells dictionary will give this output:

2 Accio
1 Lumos
3 Expelliarmus
Enter fullscreen mode Exit fullscreen mode

However, we can do a one-liner instead of the one we used with an if...else condition. We can use this version instead:

spells = {}
while True:
    try:
        spell = input()
        spells[spell] = spells.get(spell, 0) + 1
    except EOFError:
        break
Enter fullscreen mode Exit fullscreen mode

What the get function does here is literally getting the spell from the spells dictionary, and providing a default value of 0 if it is not in the dictionary. We add 1 to the whatever value that is returned by the get function to increment it. This will give the same output as above if we print each value and key of our dictionary.
Of course, as in the problem demo, printing the value and keys is only done after the user hits control-d — in other words, after our program has an EOFError. Checking the documentation as we always do before, there are many useful methods to iterate through the items of a dictionary. And, if we want our output to be sorted, well, we can literally check the documentation. Since I cannot give any more hints without giving out the solution itself, it is time to move on to the next problem.

Outdated

In the last problem of this week, we need to get a user input for a date in the month-day-year format, and output it in the year-month-day format. The input we are given can look like 9/8/1636, or September 8, 1636 (yes, an Easter egg: the year Harvard University was founded). Here, we need to a bit of splitting and some string formatting. We can use the same idea of an infinite loop like before to keep getting the user input, only returning the result when it is appropriate. Since we have two kinds of inputs to handle — the one with forward slashes (/), and the other with a comma and a space (,) —, we can use two branches for a conditional. Hints in the problem description are quite helpful on splitting a string, which you must be pretty familiar with already. We are also given a list of months in the problem explanation page, and in order to get the value of an inputted month, we can add 1 to the index of that month in that list. Lastly, we can print the formatted result and break — or, if using a function return with the string of our result. But, we need to format the day and month to be two digits, and depending on how you implement it, you can format an int to have two leading zeroes with f'{n:02}', or a string with a very handy zfill function. Taken from the documentation, what it does is this:

>>> "42".zfill(5)
'00042'
Enter fullscreen mode Exit fullscreen mode

It is self-explanatory indeed. The main thing we always need to do is error-checking, in this case the problem description does not provide a specific exception to handle and that is mostly because you can implement the solution in many different ways. But, one thing we need to make sure of is that the month and day should be within bounds, say if a user gives an input 23 in place of the month, we should prompt them again. We can make sure of this by simply returning the result string only when this conditional is met. After that, the exceptions that you need to deal with actually depends on how your code looks like, but, it mostly makes sense that we might have a ValueError, perhaps an IndexError for dealing with the months list. Since it is you as the programmer who will decide what exception to handle, checking the documentation is the first thing to do. And here, checking out the built-in exceptions is the way to go.

We are at the end of the third week, and next week we are going to finish half the course already! I cannot wait to see what problems we are going to solve for the next week on Libraries, and hope you too as well. Until then, happy coding!

Oldest comments (21)

Collapse
 
heyandre profile image
Andre Castro

That's really cool and helpful what you are doing. I'm now actually stuck in the Fuel gauge from PSET3

Collapse
 
rivea0 profile image
Eda

Thank you!
Fuel Gauge is an interesting one indeed. You can take a similar approach as the example given in the lecture. The whole try...except block can be put inside an infinite loop, and once you split the input and got the two numbers as integers, the result is simply x * (100 / y). After getting the result, we want to check if that result is more than 99 or less than 1 as long as x is less than or equal to y. If it is more than 99, we return with F, and if it is less than 1 we return with E. (Or, if you did not use a function, just break out of the loop). If none of these conditions are true, and you don't even need an else here, just return the result as a formatted string. And we can just handle the exceptions with pass. Hope this helps!

Collapse
 
heyandre profile image
Andre Castro

Thats exactly how i have done... I wrapped try...except in a while True loop and inside that after input I did an if statement and everything returns fine except that the loop keeps asking, never stops asking... Im using break after if, after elif after else...Also 4 of the checks is not passing as green

I get the following errors when checking...

:( input of 3/4 yields output of 75%
Did not find "75%" in "75%\r\nFractio..."
:( input of 503/1000 yields output of 50%
timed out while waiting for program to exit
:( input of three/four results in reprompt
expected program to reject input, but it did not
:( input of 1.5/4 results in reprompt
expected program to reject input, but it did not
:( input of 3/5.5 results in reprompt
expected program to reject input, but it did not
:( input of 5-10 results in reprompt
expected program to reject input, but it did not

Thread Thread
 
heyandre profile image
Andre Castro

I just fixed it...Passed all checks...I realised my input was before try: and after while True....I had to put inside try:

Thread Thread
 
heyandre profile image
Andre Castro

I just wanted to ask something if I may... For the input initially I wasn't sure how I could input 2 numbers and make it work like a division...so I researched and came across this method:

a, b = [int(a) for a in input("Fraction: ").split('/')]

I get most part of it...

a and b are variables where the input is stored...

int(a) converts the input to a number ... but how about b?

for a in input .... not quite sure about this part?

.split('/') splits the string in 2 string values

[ ] I guess by using these brackets we want to store it as a dictionary but for what? for the loop to work?

If you could clarify my questions I would really appreciate...
Many thanks

Thread Thread
 
rivea0 profile image
Eda • Edited

int(a) converts the input to a number ... but how about b?

This piece of code (a, b = [int(a) for a in input("Fraction: ").split('/')]) is actually a list comprehension — one of my favorites of Python. It is just a more elegant, and "pythonic" way to write for loops. Here, it asks for an input, and splits it with /, then it puts the values (converted to integers) inside a list. Then it unpacks the list with assigning the two values into variables a and b. Literally the same as this:

values = []
for a in input('Fraction: ').split('/'):
    values.append(int(a))
a = values[0]
b = values[1]
Enter fullscreen mode Exit fullscreen mode

So, the a inside the list is different from a the variable that the first value is assigned to. Giving them the same name is not a good practice in my opinion. You can change that name and it does not matter, for example, a, b = [int(x) for x in input("Fraction: ").split('/')] gives the same output.

W3Schools has a basic explanation on how list comprehensions work. You can also read about them from the official docs.

Glad you passed the tests. The whole aim of the infinite loop is just to keep getting input, but using that list comprehension example inside an infinite loop would continue asking for an input forever and keep adding the results into a list, so it wouldn't work. I think it's better to convert the values after you got the input, but it really depends on how you would like to implement it.

Thread Thread
 
heyandre profile image
Andre Castro

Thanks a lot for the clear explanation...

Collapse
 
heyandre profile image
Andre Castro • Edited

Now Im nearly done with the taqueria, but it gives me 1 error... saying input of burger leads to reprompt and is expected to reject

the structure of my code is...

variable to count the total

try statement...

inside try is the dictionary

then is the while True followed by a for loop
with break just before the except statement

except statement...
with pass inside

Collapse
 
heyandre profile image
Andre Castro

Nevermind... I actually fixed it... was due to the except statements... I was putting both errors under one except statement and check50 expects different behaviours for each error...so i just separated by having 2 except statements, one with pass and other with break

Collapse
 
rivea0 profile image
Eda

I have just seen your comment, glad you fixed it! I've also come across timeout errors with the new problem set, I guess it happens when try...except block or a loop is suffocated with many things at once and cannot exit the program properly.

Thread Thread
 
heyandre profile image
Andre Castro

Now, I'm nearly done with the grocery problem, but I just need to for example, if i input Apple Orange at the same time to print in separate lines...It prints in separate lines if I type Apple then enter, Orange then enter... but if I type Apple Orange then enter prints one next to the other...

Thread Thread
 
rivea0 profile image
Eda

Not sure if I understand it correctly, but shouldn't you enter each item separately anyway? Like the example I give in this article, Accio, Lumos, etc. all inputted one by one. So if you put two items as one input, it is going to be stored as such.

Thread Thread
 
heyandre profile image
Andre Castro • Edited

If i input one by one my code works correctly although I only get 2 green checks that the file exists and the one handling the EO error... the other errors are all the same:

:( input of "apple" and "banana" yields "1 APPLE 1 BANANA"
expected "1 APPLE\n1 BAN...", not "Input: 1 APPLE..."
:( input of "strawberry" and "strawberry" yields "2 STRAWBERRY"
expected "2 STRAWBERRY", not "Input: 1 STRAW..."
:( input of "mango", "sugar", and "mango" yields "2 MANGO 1 SUGAR"
expected "2 MANGO\n1 SUG...", not "Input: 1 MANGO..."
:( input of "tortilla" and "sweet potato" yields "1 SWEET POTATO 1 TORTILLA"
expected "1 SWEET POTATO...", not "Input: 1 TORTI..."

below is a example of my logic...

dictionary = {} 

while True:
     user_input = input("Input: ")

     dictionary[user_input] = dictionary.get(user_input, 0) + 1

     for key, value in sorted(dictionary.items()):
          print(f"{value} {key.upper()}", end=" ")
          print("\n")
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
rivea0 profile image
Eda

I implemented it with the same logic as well. But, you need to wrap everything inside a try...except block. Like, you can try to get the input and put it inside the dictionary; and only inside except EOFError you can do the for loop and printing. Also, I don't think you need to specify end inside the first print function and then print a newline. Just print(f"{value} {key.upper()})" will be going to provide a new line after each time by default, so you do not need to do anything else.

Thread Thread
 
heyandre profile image
Andre Castro

Ye I have everything wrapped inside try...except block, just didnt include that in the code I wrote here...

Thread Thread
 
heyandre profile image
Andre Castro • Edited

Amazing! I fixed it! Thanks a lot for the tip...I was around and around trying new ways and would never have guessed that the for loop should be inside the except. I even tried import sys and use sys.stdin.read().split("\n") and was working as expected, but couldn't get green checks, only yellow checks ...anyway, now looking through the code and thinking about the logic makes perfect sense...Thanks a lot! Onto the next one :)

Thread Thread
 
rivea0 profile image
Eda

Congrats! We only need to print everything after the user hits control-d (resulting in EOFError), so it makes sense to put it inside the except. It's absolutely great to see that helped!

Collapse
 
aturysbekova profile image
aturysbekova

can you guys share "outdated" code please?

Collapse
 
aristhera profile image
Gebhard • Edited

Hi @ all, I just started with outdated. I was wondering how to catch an out of range error for DD > 31 or MM > 12. I thought first splitting the user input into variables like a, b, c = input.split('/') like we're told in the hints, then converting to integers by using the modulus operator, e.g.
a, b, c = int(a) % 12, int(b) % 31, int(c). Is that alright or am I obliged to raise an exception?

Collapse
 
rivea0 profile image
Eda

Hello Gebhard,

It seems that dev.to has an issue with notifications for comments on original content, I haven't seen yours until I have just encountered it, I really apologize for the inconvenience of the delay.

You've probably already had an answer to your question by now, but I still wanted to reply.

I don't think there is any need to convert the variables to integers while splitting. A simple conditional was enough in my solution to check if the values were not out of range, and since the output was going to be a string, you could just
convert them to integers for the conditional, with something like if 0 < int(day) < 32 and 0 < int(month) < 13. I don't think it would require raising an exception here, we could just raise a ValueError for the input itself if it was faulty.

Again, I apologize for the long-delayed response, hope you're doing well.

Collapse
 
aristhera profile image
Gebhard

Hi Eda, yes I know that, happens to me all the time (overlooking messages). Don't worry about it, hope you're doing well, too