Python Forum
Dictionary lookups exercise (PyBite #109)
Thread Rating:
  • 3 Vote(s) - 2.67 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Dictionary lookups exercise (PyBite #109)
#1
I’m learning how to evaluate value-key pairs in a given dictionary.

Here is the exercise from the online course (not for credit):

Quote:First title case the passed in day argument
(so monday or MONDAY both result in Monday).

If day is not in WORKOUT_SCHEDULE, return INVALID_DAY

If day is in WORKOUT_SCHEDULE retrieve the value (workout)
and return the following:
- weekday, return TRAIN with the workout value interpolated
- weekend day (value 'Rest'), return CHILL_OUT

Examples:
- if day is Monday -> function returns 'Go train Chest+biceps'
- if day is Thursday -> function returns 'Go train Legs'
- if day is Saturday -> function returns 'Chill out!'
- if day is nonsense -> function returns 'Not a valid day'

Trivia: /etc/motd is a file on Unix-like systems that contains
a 'message of the day'

At the bottom of this forum post I’ve included some additional / supplemental background and more details about the purpose and description of this exercise and what I am trying to achieve.

Here is my first attempt:

INVALID_DAY = 'Not a valid day'
REST = 'Rest'
CHILL_OUT = 'Chill out!'
TRAIN = 'Go train {}'
WORKOUT_SCHEDULE = {
   'Friday': 'Shoulders',
   'Monday': 'Chest+biceps',
   'Saturday': 'Rest',
   'Sunday': 'Rest',
   'Thursday': 'Legs',
   'Tuesday': 'Back+triceps',
   'Wednesday': 'Core'
   }

day = 'Monday'
WORKOUT_SCHEDULE_LOWER = dict(k.lower() for k in WORKOUT_SCHEDULE.keys())
if day not in WORKOUT_SCHEDULE_LOWER.keys():
   print(INVALID_DAY)
elif day['saturday','sunday'] in WORKOUT_SCHEDULE_LOWER:
# elif day('saturday','sunday') in WORKOUT_SCHEDULE_LOWER: # Potential alternative to the above line
   print(REST)
   print(CHILL_OUT)
elif day['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] in WORKOUT_SCHEDULE_LOWER.keys():
   print(TRAIN)
Here is my current traceback:

Error:
$ python basic_workouts2.py Traceback (most recent call last): File "basic_workouts2.py", line 16, in <module> WORKOUT_SCHEDULE_LOWER = dict(k.lower() for k in WORKOUT_SCHEDULE.keys()) ValueError: dictionary update sequence element #0 has length 6; 2 is required
The problem line is #16 where I use my dictionary generator. The official Python docs explains the ValueError:

Quote:exception ValueError
Raised when an operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.

I don’t understand that at all.

So I Google ‘ValueError: dictionary update sequence element #0 has length 6; 2 is required’ which turns up this old StackOverflow question with out of date (Python 2) answers which suggest a malformed dictionary, such as using a comma instead of a colon to separate a value from a key when a dictionary is declared. My dictionary is perfectly formed, so I am not sure why I am still getting this particular error.

I gather I might not be calling the keys() method/function properly. I found all kinds of tools referred to in other 10 year old Stack Overflow answers to iterate over a dictionaries, however so many answers refer to Python 2 .iteritems() and .iterkeys() which are now deprecated in Python 3.

As you people may be able to tell, I am a Python novice so I am open to as many hints and suggestions that you people can provide. Without providing the complete answer, I am looking forward to hearing from other forum members explain how I can refine and improve my script.

The original exercise I am working on here explicitly calls for using functions and returning certain values. The exercise also calls for dictionary value interpolation. I have deliberately skipped these features but only temporarily. For now I am simply trying to implement the general task using print operations. Afterwards I will use proper return operators and work on interpolation for the TRAIN variable and such.

Here is a little bit more about what I am trying to achieve:

Quote:In this task you learn how to lookup values from a dictionary or in Python: dict.

You are presented with WORKOUT_SCHEDULE dict (constant) with keys = days and values = workouts (or rest up). Complete get_workout_motd that receives a day string, title case it so the function can receive case insensitive days, look it up in the dict and do two things:

* If the day (key) is not in the dictionary, return INVALID_DAY, we don't want this function to continue.
* If the key is in the dictionary, return CHILL_OUT or TRAIN depending if it's a REST day or not. The latter you will need to string-interpolate using format.

Also check out the docstring and tests. Have fun and keep calm and code in Python!

Update 25th of Nov 2019: previously this Bite required re-raising the KeyError, but as that's already the default behavior of a missing key in a dict, we changed the requirements to return a value instead.
Reply
#2
(Jul-03-2020, 09:01 AM)Drone4four Wrote: dict(k.lower() for k in WORKOUT_SCHEDULE.keys())
What is a dict? A dict is a structure with key-value pairs. So a multiple of 2 elements. But here you are creating a one dimensional list. You are not creating a dictionary.
Furtheron you are not making full use of the possibilities of a dictionary, you make it far too complicated. How about this:
day = 'Monday'
if day in WORKOUT_SCHEDULE:
    print(WORKOUT_SCHEDULE[day])
else:
    print(INVALID_DAY)
Output:
Chest+biceps
You will have to add something with capitalizing and make a function of it.
Reply
#3
(Jul-03-2020, 10:34 AM)ibreeden Wrote:
(Jul-03-2020, 09:01 AM)Drone4four Wrote: dict(k.lower() for k in WORKOUT_SCHEDULE.keys())
What is a dict? A dict is a structure with key-value pairs. So a multiple of 2 elements. But here you are creating a one dimensional list. You are not creating a dictionary.

Ah OK! Thank you for pointing this out, @ibreeden. I see my mistake now. I sourced my original idea for the dictionary generator from that Stack Overflow link that I shared. I made some changes, thinking that referring to only the keys was necessary. So my error indicating an issue with a malformed dictionary is accurate after all!

I changed line 16 so it now reads:

WORKOUT_SCHEDULE_LOWER = dict((k.lower(), v) for k,v in WORKOUT_SCHEDULE.items())

Is this an improvement? As far as I can tell, yes, because that dictionary traceback is now gone. Line 16 is no longer an issue.

I took some of your other advice and rearranged my conditional control structure so that the INVALD_DAY result is the last case.

Here is my latest iteration of my script in full (take note of lines 15-23):

INVALID_DAY = 'Not a valid day'
REST = 'Rest'
CHILL_OUT = 'Chill out!'
TRAIN = 'Go train {}'
WORKOUT_SCHEDULE = {
   'Friday': 'Shoulders',
   'Monday': 'Chest+biceps',
   'Saturday': 'Rest',
   'Sunday': 'Rest',
   'Thursday': 'Legs',
   'Tuesday': 'Back+triceps',
   'Wednesday': 'Core'
   }

day = 'Saturday'.lower()
WORKOUT_SCHEDULE_LOWER = dict((k.lower(), v) for k,v in WORKOUT_SCHEDULE.items())
if WORKOUT_SCHEDULE_LOWER[day] == ('monday', 'tuesday', 'wednesday', 'thursday', 'friday'):
   print(TRAIN)
elif WORKOUT_SCHEDULE_LOWER[day] == ('saturday','sunday'):
   print(REST)
   print(CHILL_OUT)
else:
   print(INVALID_DAY)
The output I’m expecting is: “Rest” but the output I am getting is: “Not a valid day”. The first two conditionals are returning False when I am trying to get the second conditional to return as True (because Saturday is a rest day).

Your final comment that I do not understand is:

Quote:You will have to add something with capitalizing and make a function of it.

I may need to need a little more guidance and further clarity on what you mean.

Thank you @ibreeden for your help and patience as I continue to work through this. More hints and suggestions are welcome.
Reply
#4
(Jul-03-2020, 02:00 PM)Drone4four Wrote: day = 'Saturday'.lower()
...
if WORKOUT_SCHEDULE_LOWER[day] == ('monday', 'tuesday', 'wednesday', 'thursday', 'friday'):
As "day" = 'saturday', WORKOUT_SCHEDULE_LOWER[day] will be evaluated to: 'Rest'. So the whole line will be evaluated to:
if 'Rest' == ('monday', 'tuesday', 'wednesday', 'thursday', 'friday'):
Reply
#5
(Jul-03-2020, 02:00 PM)Drone4four Wrote: Your final comment that I do not understand is:

Quote:
You will have to add something with capitalizing and make a function of it.

Well, from the assignment I understood it had to be a function:
(Jul-03-2020, 09:01 AM)Drone4four Wrote: Examples:
- if day is Monday -> function returns 'Go train Chest+biceps'
- if day is Thursday -> function returns 'Go train Legs'
- ...

And about the capitalizing, besides a method str.lower() there is also a str.capitalize():
Quote:Return a copy of the string with its first character capitalized and the rest lowercased.
.

So i did not understand why you wanted to create a copy of WORKOUT_SCHEDULE with the keys in lowercase. That is not necessary when you compare with day.capitalize().
Reply
#6
Based on your feedback, @ibreeden, I’ve rewritten my script from scratch. In my latest iteration, I’ve done a few things. I've:
  • wrapped my algorithm inside a function and did away with the dictionary loop
  • replaced lowercase-ing of the dictionary keys and am now using capitalize()
Here is my script now:

INVALID_DAY = 'Not a valid day'
REST = 'Rest'
CHILL_OUT = 'Chill out!'
TRAIN = 'Go train {}'
WORKOUT_SCHEDULE = {
   'Friday': 'Shoulders',
   'Monday': 'Chest+biceps',
   'Saturday': 'Rest',
   'Sunday': 'Rest',
   'Thursday': 'Legs',
   'Tuesday': 'Back+triceps',
   'Wednesday': 'Core'
   }

def get_workout_motd(day):
   if WORKOUT_SCHEDULE[day.capitalize()] == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
       return TRAIN
   elif WORKOUT_SCHEDULE[day.capitalize()] == ('Saturday','Sunday'):
       return (REST, CHILL_OUT)
   else:
       return INVALID_DAY
Here is my test script:

import pytest
from workouts1 import (get_workout_motd,
                     CHILL_OUT, INVALID_DAY)


# About parametrize: https://pybit.es/pytest-coding-100-tests.html
@pytest.mark.parametrize("day, expected", [
   ('Monday', 'Go train Chest+biceps'),
   ('monday', 'Go train Chest+biceps'),
   ('Tuesday', 'Go train Back+triceps'),
   ('TuEsdAy', 'Go train Back+triceps'),
   ('Wednesday', 'Go train Core'),
   ('wednesdaY', 'Go train Core'),
   ('Thursday', 'Go train Legs'),
   ('Friday', 'Go train Shoulders'),
   ('Saturday', CHILL_OUT),
   ('Sunday', CHILL_OUT),
   ('sundAy', CHILL_OUT),
   ('nonsense', INVALID_DAY),
   ('monday2', INVALID_DAY),
])
def test_get_workout_valid_case_insensitive_dict_lookups(day, expected):
   assert get_workout_motd(day) == expected
When I run that test script, here is my traceback: https://pastebin.com/kjt6GjjT

I put my traceback inside a pastebin because it is verbose.

As you can see in my traceback, everything fails with a KeyError. I am way off. All I understand is that my WORKOUT_SCHEDULE dictionary splicing is malformed.

What would you people suggest I try next? Any hints?

This is not homework for a course, but I am pretending that it is. So my ask is that you people provide a series of hints and general guidance rather than a full answer.

Guides I’ve leveraged this time:
Reply
#7
I think your problem stated out by incorrectly interpreting the assignment. From the assignment:
Quote:First title case the passed in day argument
(so monday or MONDAY both result in Monday).
You are asked, as the first thing in your program, to title case the passed in day assignment. I do not see where you are doing that. If the day is title cased it will match the case of the keys in the dictionary. If you get an invalid key error, you know the day is invalid, and not just a case error. So start by doing what is asked in the assignment.
day = day.capitalize()
As for a key error, that is common. Anybody who writes a program that uses a dictionary lookup and allows free-form user input will have a way to handle invalid keys. Your solution could be made to work, but it is a terrible solution. Recreating the dictionary keys in sets is brittle and error prone and lengthy. Do you think anyone would program in Python if they had to do things like this to use a dictionary? Of course not. There must be a better way. I wonder how many pages show up if you google "handle Python KeyError".

By the way, the exasperated "There has got to be a better way!" test is what I use to let me know when the approach I am using is most likely wrong and that it is time to close the editor and do some research.
Reply
#8
(Jul-09-2020, 02:56 PM)Drone4four Wrote: if WORKOUT_SCHEDULE[day.capitalize()] == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):

This will never be True; the block after this "if" will never get executed. Look careful step by step how the Python interpreter eveluates this line. Asume the actual parameter day="monday".
if WORKOUT_SCHEDULE[     day.capitalize()] == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
if WORKOUT_SCHEDULE["monday".capitalize()] == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
if WORKOUT_SCHEDULE["Monday"             ] == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
if "Chest+biceps"                          == ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
if False                                                                                              :
That is not what you want.
Reply
#9
My script is now passing 2 of 13 tests. So I’ve made some progress.

As per @deanhystad’s suggestion, I Googled ‘handle Python KeyError’ which turned up Real Python’s “Python KeyError Exceptions and How to Handle Them”. In that doc, Chad Hansen explains that there are different ways to handle a Python KeyError. The usual and most common solution is the .get() method. So I went with that. w3school’s doc covering the “Python Dictionary get() Method” was helpful too. From these docs, I learned how to retrieve a dictionary’s keys and return their corresponding value pair using .get(). For example with, my_dict.get(keyname, value), the keyname is a required parameter and value is a substitute which is returned if the keyname doesn’t exist.

Based on what I have learned above, here is what I have changed since last time:
  • I’m using the .get() method
  • I’m capitalizing the day and assigning it to a new variable which I use to call the dictionary value.
  • I’ve replaced the three instances of the equality operator with in (I got this idea from @ibreeden feedback)

Here is what my script looks like now with the above changes:

INVALID_DAY = 'Not a valid day'
REST = 'Rest'
CHILL_OUT = 'Chill out!'
TRAIN = 'Go train {}'
WORKOUT_SCHEDULE = {
   'Friday': 'Shoulders',
   'Monday': 'Chest+biceps',
   'Saturday': 'Rest',
   'Sunday': 'Rest',
   'Thursday': 'Legs',
   'Tuesday': 'Back+triceps',
   'Wednesday': 'Core'
   }

def get_workout_motd(day):
   cap_day = day.capitalize()
   x = WORKOUT_SCHEDULE.get(cap_day)
   if x in ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'):
       return (TRAIN, 'Go train {}')
   if x in ('Saturday','Sunday'):
       return CHILL_OUT, REST
   else:
       return INVALID_DAY
When I run the test script, $ python -m pytest test_workouts.py, here is my traceback in full.

I can already see part of the problem. My line 19 is malformed. What I am trying to do is return both TRAIN as a string with the key passed in as the placeholder. Or in other words, I’m not sure how to return the string of 'Go train Chest+biceps' when the dictionary key is ‘Monday’ but to also return ‘Go train Shoulders’ when the dictionary value is ‘Friday’. I’ve tried changing line 19 to: return TRAIN, 'Go train {}' (without the brackets) or to return 'Go train {TRAIN}. This part of the test is still failing. To improve this particular line, I Googled ‘passing in Python string variables’ which turned up all kinds of recent tutorials describing f-strings which are very basic and not specific to returning strings as part of a function with a dictionary value as a placeholder, which is what I need in my case. Therefore, what further hints or Google search terms might you suggest this time, @deanhystad?

Is there anything else you might suggest @ibreeden to help improve the latest iteration of my script?

What other hints and tips could you both make without giving me the solution?

Thank you both for your help and advice so far. I appreciate your patience with my novice questions.
Reply
#10
What does the WORKOUT_SCHEDULE dictionary return on rest days (Sat and Sun)?
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Type conversion and exception handling (PyBite #110) Drone4four 5 32,625 Jul-27-2020, 11:33 AM
Last Post: DeaD_EyE
  Iterating over dictionaries with the namedtuple function (PyBite #108) Drone4four 7 4,735 Jul-15-2020, 04:23 AM
Last Post: ndc85430
  Dictionary based exercise garvind25 2 1,951 Jul-12-2020, 06:53 PM
Last Post: garvind25
  "Slicing and dicing strings" - - PyBite #105 Drone4four 8 4,314 Jun-11-2020, 09:28 PM
Last Post: knackwurstbagel

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020