Part 3 - the grocery list program

Had WillHad Will
40 min read

« Previous article

Next article »

We'll create a program to simulate a grocery list. Users can add items, remove them, and print their grocery list on the screen.

First version: general outline

Let's start by creating an outline for our program. It will contain a variable groceries. For now, this is an empty list. The outline will also include an infinite loop, asking the user what to do. Options are adding a new item, removing an item, displaying the list, or exiting the program. I'll implement those later.

def main():
    """
    Create a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        print("What will you do next?")
        action = input("[P]rint - [A]dd - [Q]uit\n").lower()[0]
        while action != "p" and action != "a" and action != "q":
            action = input("Invalid input\n[P] or [A] or [Q]\n").lower()[0]
        print(action)
        # DISPATCH ON INPUT

        # ADD TO LIST

    # DISPLAY LIST
    pass

if __name__ == '__main__':
    main()

Lists

The first interesting bit in our code is groceries = []. groceries is an empty list.

We create lists with square brackets. Lists are sequences, meaning they store items in a specific order. Empty lists store zero values, and we'll see how to add or remove items. A list's items can be of different types, like ['a', print, 0, 3.14, [1, 2, 3]]. This last list contains a string, a function, an int, a float, and a list of three ints. We separate list items with commas.

Infinite while loops

The second interesting bit in our code is while True:. Since True is always true, this creates an infinite loop.

Use infinite loops when you want your program to always ask the user the same question and act on his answer.

Here's how the loop works:

  1. Check if the expression after the while keyword is true: the expression in question is True. So, the test will always pass.

  2. Run every line of the block under while, in order.

  3. Loop back to the while statement.

Input logic

What do ou think the [0] in action = input("[P]rint - [A]dd - [Q]uit\n").lower()[0] means?

Let's analyze it little by little.

input() creates a string from what the user types. lower() converts it to lowercase, so it is still a string.

A string is also a sequence, like a list. This means you can access individual characters by indexing into the string. Here's how you index into any sequence in Python:

<expression evaluating into a sequence>[index]

Let's open our Python interpreters and play with indexing:

>>> l = [1, 2, 3]

>>> l[0]

1

>>> l[1]

2

This is indexing from the beginning of the sequence. You can do it from the other end. The index of the last item is -1.

>>> l[-1])

3

>>> l[-2]

2

Let's get back to our mysterious expression action = input("[P]rint - [A]dd - [Q]uit\n").lower()[0]. action stores the first letter of the user input in lowercase.

Here's how the program works for now:

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [Q]uit
P
p
What will you do next?
[P]rint - [A]dd - [Q]uit
p
p
What will you do next?
[P]rint - [A]dd - [Q]uit
print
p
What will you do next?
[P]rint - [A]dd - [Q]uit
a
a
What will you do next?
[P]rint - [A]dd - [Q]uit
q
q
What will you do next?
[P]rint - [A]dd - [Q]uit
z
Invalid input
[P] or [A] or [Q]
zzz
Invalid input
[P] or [A] or [Q]
a
a

Second version: Refactoring and empty input

Our first version contained a lot of input functionality in the main function. As we add more functionality, our main function will become messy. That's why we'll tuck it away into a helper function, get_user_input(). Check it out:

def get_user_input():
    """
    Prompt the user for an action and validate the input.
    Returns one of the valid options: 'p', 'a', or 'q'.
    """
    valid_actions = ["p", "a", "q"]

    print("What will you do next?")
    action = input("[P]rint - [A]dd - [Q]uit\n").strip().lower()

    while not action or action[0] not in valid_actions:
        action = input("Invalid input. Please choose [P], [A], or [Q].\n").strip().lower()

    return action[0]

def main():
    """
    Create a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        action = get_user_input()
        print(action)

        # DISPATCH ON INPUT

        # ADD TO LIST

    # DISPLAY LIST
    pass

if __name__ == '__main__':
    main()

Separating your programs into functions also helps with testing.

Another look at while loops

What does this mean?

while not action or action[0] not in valid_actions:
    action = input("query").strip().lower()

It means that if

  • the user didn't give any input (not action)

  • or the input's first letter is neither 'p', 'a', or 'q' (action[0] not in valid_actions)

the code will keep asking for input (action = input()).

Sequence membership

The code we examined in the paragraph above contained not in valid_actions. The in keyword checks if a specific item is in a sequence (in this case, a list). The result is a boolean: either True or False.

For example:

fruits = ["apple", "banana", "cherry"]
print("apple" in fruits)  #Output: True
print("kiwi" in fruits)  #Output: False

It also works with strings and is case-sensitive:

text = "hello world"
print("hello" in text)  # Output: True
print("Hello" in text)  # Output: False
print("goodbye" in text)  # Output: False

Testing this version

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [Q]uit
p
p
What will you do next?
[P]rint - [A]dd - [Q]uit
a
a
What will you do next?
[P]rint - [A]dd - [Q]uit
q
q
What will you do next?
[P]rint - [A]dd - [Q]uit
z
Invalid input. Please choose [P], [A], or [Q].
zz
Invalid input. Please choose [P], [A], or [Q].

Invalid input. Please choose [P], [A], or [Q].
     a
a
What will you do next?
[P]rint - [A]dd - [Q]uit

Invalid input. Please choose [P], [A], or [Q].
zeijfzefido
Invalid input. Please choose [P], [A], or [Q].
q
q

3rd version: adding functionality

Now we have functioning user inputs, we'll add the core functionality. We could have gone the other way around without any problem.

# I skipped the code for get_user_input()
# as the function hasn't changed.

def get_item():
    """
    Get item to add to grocery list.
    """
    grocery = input("Add a grocery to the list: ")
    while not grocery:
        grocery = input("Please add a grocery... ")
    return grocery

def display(groceries):
    print(groceries)

def main():
    """
    Create a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        action = get_user_input()

        # DISPATCH ON INPUT
        if action == 'q':
            break
        elif action == 'a':
            grocery = get_item()
            groceries.append(grocery)
        elif action == 'p':
            display(groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

Let's test it:

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [Q]uit
p
[]
What will you do next?
[P]rint - [A]dd - [Q]uit
a
Add a grocery to the list: carrots
What will you do next?
[P]rint - [A]dd - [Q]uit
p
['carrots']
What will you do next?
[P]rint - [A]dd - [Q]uit
a
Add a grocery to the list: tomatoes
What will you do next?
[P]rint - [A]dd - [Q]uit
p
['carrots', 'tomatoes']
What will you do next?
[P]rint - [A]dd - [Q]uit
q
['carrots', 'tomatoes']

Conditional dispatching

Look at the if/elif/else branches we created. Each test checks if the user's message (stored in action) corresponds to some command. The program chooses which block of code to execute depending on the input. That's called conditional dispatching.

Let me tell you about the more general computer science concept of dispatching.

Dispatching means you 'send' the flow of the program to a specific destination based on input. Think of it like a railway system: a train switch directs the train to the correct track.

Another analogy to think about dispatching is ordering food at a restaurant. In this case, the customer is the user. He uses the menu to make a choice (an input to the restaurant). The server takes note of the order and dispatches it to the kitchen, where the food gets cooked. The customer receives the dish as an output.

Dispatching using if/elif/else conditionals suits small programs like ours. Later in this post, we'll see another option.

Quitting the infinite loop

If our while True: loop is infinite, how can the user quit the program after typing "Quit"?

We used a break statement to exit the loop. Unlike with a return statement, the code leaves the while loop but not the function. display(groceries) still has to run before the program ends for good.

Appending to a list

The way we add an item to the grocery list is to add it at the end of the list.

Python lists have a method called append() to do that. It's a bit rigid in the sense that the elements always get added at the end of the list.

If you want to add or insert an element somewhere else, you can use the method insert(). insert() takes two arguments: 1/ the index before which you want to insert your element and 2/ the element.

>>> x = [1, 2, 4]
>>> x.insert(2, 3)
>>> print(x)
[1, 2, 3, 4]

What happens when you add the same element twice? Can you find a way to prevent this?

4th version: removing from a list, code reuse

We can add groceries to the list, but what if we want to remove them? Adding that functionality with how we structured our code will be easy. First, we'll update get_user_input to accept "r" as a valid option. Then, we'll update main so we have another test in the conditional dispatching code.

Removing a grocery is very much like adding one. You ask the user what grocery he wants to remove, then remove it. We already have the code to ask the user for a grocery. It's called get_item(). Reusing function is a good design practice. It makes the code more concise and thus less prone to bugs.

While we're at it, let's format the output created by display() with a for loop.

def get_user_input():
    """
    Prompt the user for an action and validate the input.
    Returns one of the valid options: 'p', 'a', or 'q'.
    """
    valid_actions = ["p", "a", "q", "r",]

    print("What will you do next?")
    action = input("[P]rint - [A]dd - [R]emove - [Q]uit\n").strip().lower()

    while not action or action[0] not in valid_actions:
        action = input("Invalid input. Please choose [P], [A], [R], or [Q].\n").strip().lower()

    return action[0]

def get_item():
    """
    Get item to add to or remove from the grocery list.
    """
    grocery = input("Type the grocery: ")
    while not grocery:
        grocery = input("Please type a grocery... ")
    return grocery

def display(groceries):
    """
    Show the grocery list.
    """
    if groceries:
        display_text = "Groceries:"
        for grocery in groceries:
            display_text = display_text + "\n\t" + grocery
        print(display_text)
    else:
        print("Te grocery list is empty...")

def main():
    """
    Manage a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        action = get_user_input()

        # DISPATCH ON INPUT
        if action == 'q':
            break
        elif action == 'a':
            grocery = get_item()
            groceries.append(grocery)
        elif action == 'r':
            grocery = get_item()
            groceries.remove(grocery)
        elif action == 'p':
            display(groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

Let's see if it works.

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
Te grocery list is empty...
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery: carrot
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery: tomatoes
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
Groceries:
    carrot
    tomatoes
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery: steak
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery: carrot
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
Groceries:
    tomatoes
    steak
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
q
Groceries:
    tomatoes
    steak

.remove()

To remove an item from a list, use the remove method. It takes the item to remove as an argument.

Notice there's a nice symmetry between adding a grocery and removing one.

for grocery in groceries

It's good Python style to give plural names to sequences. In this case, we have a list named groceries.

When you iterate through the sequence, you'll look at each member of that sequence. It's customary to name sequence members using the singular form of the sequence name. Here it is grocery.

This convention ensures your Python code is simple and readable. for grocery in groceries is better than, say, for food in grocery_list, or for i in l.

5th version: adding quantities

We can add new groceries to our list. But it looks like this: 'apples', 'macaroni', 'pepper', etc. If you go shopping, you want to know how many kiwis to buy! Let's break our code by modifying the way we represent grocery lists.

One naive way to do this is to keep the info in strings. It would look like this:

["apples 3", "tomatoes 5", "celery 2"]

While this looks simple at first, this solution raises a problem. It's not immediately evident how you could update the list. Say your list contains 3 carrots. You realize you made a mistake and want to add 7 more.

Your code would need to

  1. extract the quantity from the string

  2. convert it to a number

  3. add 7

  4. convert the new amount to a string

  5. create a new well-formatted string containing the grocery and the quantity

  6. store it into the list at the right place

That's a top-of-my-head, not-given-much-thought estimation, but I already hate it.

As an exercise, you can think about what it would take to display the grocery and the quantity in reverse order. The output string would look like "The list contains 3 carrots". i.e., you display the amount first, while the internal representation is "carrots 3".

So we want something much more flexible and natural. We want the name of the grocery as a text string while the quantity is a number. We also want to access them in isolation if needed.

Each pair will be a list of the grocery and the quantity. So, the grocery list becomes a list of lists.

[['apples', 3], ['potatoes', 8], etc.]

Note: This change breaks functionality. I'll fix it later. Now, we want to explore in a simple manner and flesh out our vision for a better grocery list. I'll only change the adding part of main and get_item().

In main() we change our grocery adding part to:

elif action == 'a':
    grocery = get_item()
    print(f"""
    grocery: {grocery}
    grocery type: {type(grocery)}
    """)

And we also redefine get_item():

def get_item():
    """
    Get item to add to or remove from the grocery list.
    """
    input_string = input("Type the grocery and the quantity: ").strip().lower()
    while not input_string:
        input_string = input("Please type something... ")
    parts = input_string.rsplit(' ', 1)
    print(f"""
    parts: {parts}
    parts type: {type(parts)}
    """)
    return parts

List to string

The get_item() function contains one line converting strings to a list. This line of code is parts = input_string.rsplit(' ', 1). It splits the string, using a single space (' ') as a delimiter between the grocery and the quantity. The second argument is the maxsplit. It's the largest number of splits allowed on the strings. The resulting list will contain at most maxsplits + 1 items.

Strings have another similar method for splitting, split(). Why didn't we use it? split() works from the left of the string, while rsplit() works from the right. We know for sure the quantity is a single number, but the grocery item could be several words. For example, we could have a user input "Swiss cheese 2", or "Black Angus ribeye steak 1". It would be possible to start from the left of the string and work our way to the last whitespace separator. But the code would get complex fast.

The result of splitting a string is a list of strings. We still need to get our list of a grocery string and a quantity integer. That's ok for now. This step of the code is to make sure we got the string-splitting code right. Since I want to show you how you can create a program step by step, I insist on doing it in little incremental steps. This way, you get to test every change you make, and you're always confident about your code.

More on function return values

As you can see, a function like get_item() can return complex objects, like lists. You don't need to jump through hoops to do this. It works like it does for numerical return values. Consider yourself lucky you're learning Python. Not every programming language is as easy to use.

Finalizing get_item()

Like I said, we want our grocery item list to contain a string and an integer.

Also, I'll add some more input validation code.

def get_item():
    """
    Get item and quantity to add to or remove from the grocery list.
    """
    input_string = input("Type the grocery and the quantity: ").strip().lower()
    while not input_string:
        input_string = input("Please type something... ")
    parts = input_string.rsplit(' ', 1)
    if len(parts) != 2:
        print("Wring number of arguments in command... Retry")
        return None
    parts[1] = int(parts[1])
    print(f"""
    parts: {parts}
    parts type: {type(parts)}
    """)
    return parts

As usual, let’s try our program.

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: apples 2

    parts: ['apples', 2]
    parts type: <class 'list'>


            grocery: ['apples', 2]
            grocery type: <class 'list'>

What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: apples
Wring number of arguments in command... Retry

            grocery: None
            grocery type: <class 'NoneType'>

As you see, you can access and change a list's elements by using their index.

6th version: the rest of the program

Now that our program can get user input and turn it into our new data structure, we can adapt the rest of the program.

Now we link each grocery to its quantity, adding or removing a grocery takes another meaning. We cannot just append or remove a grocery anymore. When adding a grocery, you must first check if it's already in the list. If that's the case, you update the quantity. If not, you create a new entry in the grocery list.

The same principle applies to removing groceries.

def get_user_input():
    """
    Prompt the user for an action and validate the input.
    Returns one of the valid options: 'p', 'a', or 'q'.
    """
    valid_actions = ["p", "a", "q", "r",]

    print("What will you do next?")
    action = input("[P]rint - [A]dd - [R]emove - [Q]uit\n").strip().lower()

    while not action or action[0] not in valid_actions:
        action = input("Invalid input. Please choose [P], [A], [R], or [Q].\n").strip().lower()

    return action[0]

def get_item():
    """
    Get item and quantity to add to or remove from the grocery list.
    Returns either a list of the form ['grocery string', qty], where qty is an int,  or None if the number of tokens in the string was wrong.
    """
    input_string = input("Type the grocery and the quantity: ").strip().lower()
    while not input_string:
        input_string = input("Please type something... ")
    parts = input_string.rsplit(' ', 1)
    if len(parts) != 2:
        print("Wring number of arguments in command... Retry")
        return None
    parts[1] = int(parts[1])
    return parts

def grocery_exists(groceries, item):
    for grocery in groceries:
        if grocery[0] == item:
            return True
    return False

def increment_qty(groceries, item, qty):
    for grocery in groceries:
        if grocery[0] == item:
            grocery[1] = grocery[1] + qty
    return groceries

def decrement_qty(groceries, item, qty):
    increment_qty(groceries, item, -qty)

def add_grocery(groceries, to_add):
    grocery = to_add[0]
    qty = to_add[1]

    if grocery_exists(groceries, grocery):
        increment_qty(groceries, grocery, qty)
    else:
        groceries.append(to_add)

    return groceries

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if grocery_exists(groceries, grocery):
        decrement_qty(groceries, grocery, qty)

    return groceries

def display(groceries):
    """
    Show the grocery list.
    """
    if groceries:
        print(groceries)
    else:
        print("The grocery list is empty...")

def main():
    """
    Manage a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        action = get_user_input()

        # DISPATCH ON INPUT
        if action == 'q':
            break
        elif action == 'a':
            to_add = get_item()
            if to_add:
                groceries = add_grocery(groceries, to_add)
        elif action == 'r':
            to_remove = get_item()
            if to_remove:
                groceries = remove_grocery(groceries, to_remove)
        elif action == 'p':
            display(groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

That's a lot of changes!

Starting from main, you'll notice we call two functions, add_grocery and remove_grocery. These two functions take the same arguments and return the grocery list.

def add_grocery(groceries, to_add):
    grocery = to_add[0]
    qty = to_add[1]

    if grocery_exists(groceries, grocery):
        increment_qty(groceries, grocery, qty)
    else:
        groceries.append(to_add)

    return groceries

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if grocery_exists(groceries, grocery):
        decrement_qty(groceries, grocery, qty)

    return groceries

They are similar in their structure. First, they unpack the grocery argument. The name of the grocery goes in grocery and the quantity in qty. They then both check if the grocery item already exists in the list by calling grocery_exists. If the result is True, we only need to change its quantity, so we call increment_qty and decrement_qty. In the case of adding a grocery, there's the case where it doesn't already exist. In that case, we create it by using append. There's no need to bother with this in the remove_grocery function. Both functions return the grocery list.

add_grocery and remove_grocery call increment_qty and decrement_qty respectively. When writing those last two functions, I figured they did similar things. So, one function calls the other.

def decrement_qty(groceries, item, qty):
    increment_qty(groceries, item, -qty)

Let's check if the code works.

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
The grocery list is empty...
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: tomatoes 2
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: carrots 8
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
[['tomatoes', 2], ['carrots', 8]]
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: carrots 3
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: spinach 10
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
[['tomatoes', 2], ['carrots', 5]]
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
q
[['tomatoes', 2], ['carrots', 5]]
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: tomatoes 100
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
[['tomatoes', -98], ['carrots', 5]]

It works great, except for that last operation.

Nested indexing

This will fix the problem of negative quantities:

def decrement_qty(groceries, item, qty):
    for i in range(0, len(groceries)):
        if groceries[i][0] == item:
            if groceries[i][1] - qty > 0:
                groceries[i][1] = groceries[i][1] - qty
            else:
                del groceries[i]
    return groceries

It works better now:

[['carrots', 5], ['tomatoes', 10]]
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: carrots 2
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: spinach 1
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: tomatoes 100
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
[['carrots', 3]]

In the last change, there are some weird bits of code groceries[i][0] and groceries[i][1].

Let's generalize it to my_list[i][j]. You read this code as 'the jth element of the ith element of my_list.' It will be easier to look at an example.

>>> my_list = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9]
... ]
>>> my_list[0]
[1, 2, 3]

This shows that the first index gives you a list.

>>> my_list[0][0]
1

The second index selects an element in the list.

Now we see that groceries[i][0] is the first element (the grocery's name) of each grocery. In the same way, groceries[i][1] is the second element (the grocery's quantity) of each grocery.

The del keyword

The del keyword destroys an item. We used it on elements of our grocery list, but it also works on variables and other Python objects.

7th version: tidying things up

Here's the current version of our program. Please review it and make sure you understand it in full. Before long, we'll shake things up with big modifications.

def get_user_input():
    """
    Prompt the user for an action and validate the input.
    Returns one of the valid options: 'p', 'a', or 'q'.
    """
    valid_actions = ["p", "a", "q", "r",]

    print("What will you do next?")
    action = input("[P]rint - [A]dd - [R]emove - [Q]uit\n").strip().lower()

    while not action or action[0] not in valid_actions:
        action = input("Invalid input. Please choose [P], [A], [R], or [Q].\n").strip().lower()

    return action[0]

def get_item():
    """
    Get item and quantity to add to or remove from the grocery list.
    Returns either a list of the form ['grocery string', qty], where qty is an int,  or None if the number of tokens in the string was wrong.
    """
    input_string = input("Type the grocery and the quantity: ").strip().lower()
    while not input_string:
        input_string = input("Please type something... ")
    parts = input_string.rsplit(' ', 1)
    if len(parts) != 2:
        print("Wrong number of arguments in command... Retry")
        return None
    parts[1] = int(parts[1])
    return parts

def grocery_exists(groceries, item):
    """
    Check if a particular item exists in the grocery list.
    """
    for grocery in groceries:
        if grocery[0] == item:
            return True
    return False

def increment_qty(groceries, item, qty):
    """
    add qty to the number of item in groceries.
    """
    for grocery in groceries:
        if grocery[0] == item:
            grocery[1] = grocery[1] + qty
    return groceries

def decrement_qty(groceries, item, qty):
    """
    remove qty from the number or item in groceries.
    If it results in a number less to or equal to zero, remove item.
    """
    for i in range(0, len(groceries)):
        if groceries[i][0] == item:
            if groceries[i][1] - qty > 0:
                groceries[i][1] = groceries[i][1] - qty
            else:
                del groceries[i]
    return groceries

def add_grocery(groceries, to_add):
    """
    either create an new grocery item in groceries or increment the existing grocery's qty.
    """
    grocery = to_add[0]
    qty = to_add[1]

    if grocery_exists(groceries, grocery):
        increment_qty(groceries, grocery, qty)
    else:
        groceries.append(to_add)

    return groceries

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if grocery_exists(groceries, grocery):
        decrement_qty(groceries, grocery, qty)

    return groceries

def display(groceries):
    """
    Show the grocery list.
    """
    if groceries:
        print("Grocery list:")
        for grocery in groceries:
            print(f"- {grocery[0]}: {grocery[1]}")
    else:
        print("The grocery list is empty...")

def main():
    """
    Manage a grocery list.
    """

    groceries = []

    while True:
        # INPUT
        action = get_user_input()

        # DISPATCH ON INPUT
        if action == 'q':
            break
        elif action == 'a':
            to_add = get_item()
            if to_add:
                groceries = add_grocery(groceries, to_add)
        elif action == 'r':
            to_remove = get_item()
            if to_remove:
                groceries = remove_grocery(groceries, to_remove)
        elif action == 'p':
            display(groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

8th version: dictionnaries

Up to now, our grocery list is a list of lists. Each grocery is a list containing a name and a quantity. That's what's called key-value pairs. The name is the key, and the quantity is the value. Python has a built-in data structure for key-value pairs: dictionaries.

Dictionaries

You define a dictionary with curly brackets: {}

Here's the form of an example dictionary:

my_dict = {
<key_1>: <value_1>,
<key_2>: <value_2>,
...
<key_n>: <value_n>
}

Each key matches a value. Note the colon (:) separating the keys from the values.

Access a value by its key. Using our example above, my_dict[key_1] is value_1, my_dict[key_2] is value_2, etc.

The first draft with dicts

I'll convert the program fast and polish it later in our next version. You'll see how converting from a custom data structure to a built-in type makes the code tighter.

def get_user_input():
    """
    Prompt the user for an action and validate the input.
    Returns one of the valid options: 'p', 'a', or 'q'.
    """
    valid_actions = ["p", "a", "q", "r",]

    print("What will you do next?")
    action = input("[P]rint - [A]dd - [R]emove - [Q]uit\n").strip().lower()

    while not action or action[0] not in valid_actions:
        action = input("Invalid input. Please choose [P], [A], [R], or [Q].\n").strip().lower()

    return action[0]

def get_item():
    """
    Get item and quantity to add to or remove from the grocery list.
    Returns either a tuple of the form ('grocery string', qty), where qty is an int,  or None if the number of tokens in the string was wrong.
    """
    input_string = input("Type the grocery and the quantity: ").strip().lower()
    while not input_string:
        input_string = input("Please type something... ")
    parts = input_string.rsplit(' ', 1)
    if len(parts) != 2:
        print("Wrong number of arguments in command... Retry")
        return None
    parts[1] = int(parts[1])
    return parts[0], parts[1]

def add_grocery(groceries, to_add):
    """
    either create an new grocery item in groceries or increment the existing grocery's qty.
    """
    grocery = to_add[0]
    print(f"grocery: {grocery}")
    qty = to_add[1]
    print(f"qty: {qty}")

    if groceries[grocery]:
        groceries[grocery] = groceries[grocery] + qty
    else:
        groceries[grocery] = qty

    return groceries

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if groceries[grocery]:
        groceries[grocery] = groceries[grocery] - qty

    return groceries

def display(groceries):
    """
    Show the grocery list.
    """
    if groceries:
        print(groceries)
    else:
        print("The grocery list is empty...")

def main():
    """
    Manage a grocery list.
    """

    groceries = {}

    while True:
        # INPUT
        action = get_user_input()

        # DISPATCH ON INPUT
        if action == 'q':
            break
        elif action == 'a':
            to_add = get_item()
            if to_add:
                groceries = add_grocery(groceries, to_add)
        elif action == 'r':
            to_remove = get_item()
            if to_remove:
                groceries = remove_grocery(groceries, to_remove)
        elif action == 'p':
            display(groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

Before we test the code, I have to explain a point of theory.

Functions with several return values

I changed the get_item function to return two values: parts[0] and parts[1], separated by a comma. We'll see in a future post that this is a special Python data structure called a tuple. The two values get stored as a tuple in the variable to_add. to_add then gets passed as an argument to add_grocery and remove_grocery. These two last functions access the two values by index.

We could have returned parts[0] and parts[1] as a list, like before. It is better Python style to use a tuple, though.

testing

Before we try the program, can you already guess its problems? (Hint: the predicates in the if clauses get evaluated). If you caught the error, congrats! You're smarter than me. This was a bug I got while writing this program for you. I included it in this post so you can get accustomed to reading error output from Python. Remember, it's normal to make errors.

 What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: tomatoes 2
grocery: tomatoes
qty: 2
Traceback (most recent call last):
  File "~/Project3.py", line 96, in <module>
    main()
  File "~/Project3.py", line 82, in main
    groceries = add_grocery(groceries, to_add)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "~/Project3.py", line 40, in add_grocery
    if groceries[grocery]:
       ~~~~~~~~~^^^^^^^^^
KeyError: 'tomatoes'

As we see, we get an error as soon as we try to add a grocery. Why is that? Our code hits the line if groceries[grocery] and tries to give you the value associated with the key grocery. This is impossible since we still need to add the grocery. So we receive an error of type KeyError.

9th version: correcting bugs

def add_grocery(groceries, to_add):
    """
    either create an new grocery item in groceries or increment the existing grocery's qty.
    """
    grocery = to_add[0]
    qty = to_add[1]

    if not grocery in groceries:
        groceries[grocery] = qty
    else:
        groceries[grocery] = groceries[grocery] + qty

    return groceries

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if groceries.get(grocery):
        groceries[grocery] = groceries[grocery] - qty

    return groceries

The add_grocery fix contains the more elegant solution. Try to use if not key in dict to test whether a specific key is not in the dictionary.

We didn't use this clean approach in remove_grocery to show how to use the get() method. When used on a dictionary, it takes a key as a parameter. It returns the value associated with it if the key exists. It also takes an optional argument (not specified here), which is the value to return if the key does not exist. It is None by default. This is why we can use if groceries.get(grocery) without getting an error when the key does not exist.

Prefer the first choice, shown in add_grocery. It is cleaner. As an exercise, rewrite remove_grocery using if key in dict.

Let's try our program:

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: tomatoes 10
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: celery 2
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: tomatoes 4
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: potatoes 100
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
{'tomatoes': 6, 'celery': 2}
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: celery 5
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
q
{'tomatoes': 6, 'celery': -3}

The last line of output shows we can have a negative quantity, which is impossible. Instead, we'd like to remove the item when the quantity gets negative.

def remove_grocery(groceries, to_remove):
    grocery = to_remove[0]
    qty = to_remove[1]

    if groceries.get(grocery) and groceries.get(grocery) > qty:
        groceries[grocery] = groceries[grocery] - qty
    else:
        groceries.pop(grocery, None)

    return groceries

Two things changed: we now test for the grocery's quantity and introduce the pop method.

Popping removes a key-value pair and returns the value. If you can't find a key, pop either returns a default value or an error. Here, the default value is None.

Let's test it:

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: celery 2
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
The grocery list is empty...
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: tomato 10
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: tomato 5
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
{'tomato': 5}
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: tomato 100
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
p
The grocery list is empty...
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
a
Type the grocery and the quantity: spinach 16
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
r
Type the grocery and the quantity: rose 1
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
q
{'spinach': 16}

Now, let's create a nice display for our grocery list:

def display(groceries):
    """
    Show the grocery list.
    """
    if groceries:
        print("Grocery list:")
        for grocery, qty in groceries.items():
            print(f"{grocery}: {qty}")
    else:
        print("The grocery list is empty...")

This shows a common way to iterate through a dictionary: for grocery, qty in groceries.items(). You'll sometimes see it like this for k, v in my_dict.items(). k and v are short for key and value.

groceries.items() gives you key-value pairs as tuples (remember tuples? We saw them when a function returned several values) inside a "list-like" structure. If you think this is opaque, I don't blame you. Don't panic. The primary use of the items method is iterating through key-value pairs like we did.

Now let's see if it works.

[P]rint - [A]dd - [R]emove - [Q]uit
p
Grocery list:
onions: 4
tomatoes: 5

10th (final version): dispatch tables

Our main function contains conditional logic to dispatch the flow of the program. Given our example, it is clear, concise, and easy to expand. Let's fast forward 10 years of never saying no to new features. Our poor conditional dispatch now crumbles under hundreds of repetitive elif clauses. We want a new way to dispatch the program flow.

Enter dispatch tables:

def main():
    """
    Manage a grocery list.
    """

    groceries = {}

    actions = {
        'a': add_grocery,
        'r': remove_grocery,
        'p': display,
    }

    while True:
        # INPUT
        action = get_user_input()

        if action == 'q':
            break
        elif action in actions:
            actions[action](groceries)
        else:
            pass

    # DISPLAY LIST
    display(groceries)

if __name__ == '__main__':
    main()

We created an actions dictionary where every signal ('a', 'r', 'p') maps to a corresponding function name. In the while loop, where the dispatching happens, we index into this dictionary. This is done by actions[action]. The result is a function. We pass this function a parameter: groceries. Let's use an example:

The user enters 'Add'. get_user_input translates it to 'a', which goes into action

we try if action == 'q', which evals to False.

We then try action in actions. Since action is 'a' and 'a' is one of the keys in actions, this evals to True.

So, we index into action with actions[action]. This is the same as actions['a']. actions['a'] is add_grocery. If we substitute add_grocery for actions[action] we get add_grocery(groceries).

I redefined add_groceries and remove_groceries to accept one argument. The display function stays the same.

def add_grocery(groceries):
    """
    either create an new grocery item in groceries or increment the existing grocery's qty.
    """
    to_add = get_item()

    grocery = to_add[0]
    qty = to_add[1]

    if not grocery in groceries:
        groceries[grocery] = qty
    else:
        groceries[grocery] = groceries[grocery] + qty


    return groceries

def remove_grocery(groceries):
    to_remove = get_item()

    grocery = to_remove[0]
    qty = to_remove[1]

    if groceries.get(grocery) and groceries.get(grocery) > qty:
        groceries[grocery] = groceries[grocery] - qty
    else:
        groceries.pop(grocery, None)

    return groceries

Recap & additional info

Lists

Lists are ordered collections. Each list item has an index through which you can access it. Indexing starts at 0. You can also access items by counting from the end. In that case, the last element's index is -1.

>>> my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> my_list[0]
'a'
>>> my_list[-1]
'g'

Lists can grow or shrink as needed and contain elements of different data types. They can contain other lists (nested lists).

A property of lists we haven't explored is slicing. You can extract a "slice" of a list by giving a range of indices. To do this, you use the colon (:) operator. You can also specify the interval (the "step") between elements in the slice. The default is 1. The syntax is list[start:stop:step]. Here, 'start' is the index of the first element, while 'stop' is the index where the slice ends. Note that stop is exclusive: the slice includes the elements up to but does not include that index.

>>> my_list[0:3]
['a', 'b', 'c']
>>> my_list[2:6:2]
['c', 'e']

You can omit start or stop. In that case, the slice will start at the beginning of the original list or end at the last element.

>>> my_list[:3]
['a', 'b', 'c']
>>> my_list[2:]
['c', 'd', 'e', 'f', 'g']
>>> my_list[2::2]
['c', 'e', 'g']

You can mix slicing with negative indices. Beware, as your code will become less readable and more prone to bugs. (i.e., try not to do it)

>>> my_list[-4:-2]
['d', 'e']

Let's discuss some useful methods you can use on lists. Some of them we've seen, others not yet. For those new methods, don't worry if you don't remember them yet. We'll see them in action soon enough.

  1. append(item): adds the item at the end of the list.

  2. extend(iterable): like append, but instead of adding a single item, you add the items of a sequence (e.g., of a list).

  3. insert(index, item): inserts the item at the index.

  4. remove(item): remove the first item in the list. Keep in mind that remove won't look for other occurrences once it has found its first target.

  5. pop(index): removes the element at the index. If you don't specify an index, the last element of the list is the default.

  6. clear(): turn the list into an empty list.

  7. index(item, start, end): return the index of the first item occurrence in the list. start and end are optional but can limit the search.

  8. count(item): return the number of times the item appears in the list.

  9. sort(key=None, reverse=False): the default is ascending order. The parameters are optional.<key> is a one-argument function to customize sorting. <reverse> means you reverse the chosen sorting order.

  10. reverse(): works as expected.

>>> my_list.clear()
[]

>>> my_list.append(1)
[1]

>>> my_list.extend([2, 3, 4, 5, 6, 7])
[1, 2, 3, 4, 5, 6, 7]

>>> my_list.remove(3)
[1, 2, 4, 5, 6, 7]

>>> my_list.insert(2, 3)
[1, 2, 3, 4, 5, 6, 7]

>>> my_list.pop()
7

>>> my_list
[1, 2, 3, 4, 5, 6]

>>> my_list.index(4)
3

>>> my_list.count(7)
0

>>> my_list.count(1)
1

>>> my_list.remove(3)
[1, 2, 4, 5, 6]

>>> my_list.append(3)
[1, 2, 4, 5, 6, 3]

>>> my_list.sort()
[1, 2, 3, 4, 5, 6]

>>> my_list.reverse()
[6, 5, 4, 3, 2, 1]

Lists are a bread-and-butter data structure in Python. We'll see more of them in future posts, so don't worry if you can't remember everything at once.

Dictionaries

Dictionaries are also collections. In older Python versions, dictionaries were unordered. That changed in June 2018, when version 3.7 came out. When encountering Python code in the wild, there's a chance it uses an older version.

This doesn't matter much since we access dictionary items as key-value pairs. We don't often care about item ordering.

>>> my_dict = {}
{}
>>> my_dict = {
...     'a': 1,
...     'b': 2,
...     'c': 3
... }
{'a': 1, 'b': 2, 'c': 3}
>>> my_dict['a']
1
>>> my_dict['d'] = 4
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

As we access data through the keys, these have to be unique.

>>> my_dict_bad = {
...     'a': 1,
...     'b': 2,
...     'a': 3,
... }
{'a': 3, 'b': 2}

You see that as we reuse the key 'a' in my_dict_bad, Python overwrites the value of 'a'.

As shown above, you can access a specific value using its key, but that is rigid. If you try to access a key that does not exist, you'll receive an error.

>>> my_dict['f']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'f'

Here's a list of useful dictionary methods:

  1. get(key, default=None): retrieves the value for the key. You won't get any error if the key doesn't exist, but it will return the default instead. You can specify a custom default argument. It will be None if you don't, which is a sensible choice.

  2. keys(): returns a Python object containing all the keys.

  3. values(): returns a Python object containing all the values.

  4. items(): returns a Python object containing all key-value pairs as tuples.

  5. update(other_dictionary): adds the key-value pairs from other_dictionary. It will overwrite existing key-value pairs if necessary.

  6. pop(key, default=None): removes a key-value pair. If it exists, pop returns the value. Else, it returns default.

  7. clear(): turns the dictionary into an empty one.

  8. setdefault(key, default=None): if the key already exists, returns its value. Else, creates a new key-value pair with default as the value and returns default.

  9. fromkeys(iterable, value=None): creates a dictionary from the keys stored in <iterable>. It will populate the values with value.

>>> my_dict.clear()
{}

>>> my_list
[6, 5, 4, 3, 2, 1]

>>> my_dict = my_dict.fromkeys(my_list)
{6: None, 5: None, 4: None, 3: None, 2: None, 1: None}

>>> my_dict.keys()
dict_keys([6, 5, 4, 3, 2, 1])

>>> my_dict.values()
dict_values([None, None, None, None, None, None])

>>> my_dict.items()
dict_items([(6, None), (5, None), (4, None), (3, None), (2, None), (1, None)])

>>> my_dict[6] = 'six'
>>> my_dict.items()
dict_items([(6, 'six'), (5, None), (4, None), (3, None), (2, None), (1, None)])

>>> my_dict.setdefault(7)
>>> my_dict.items()
dict_items([(6, 'six'), (5, None), (4, None), (3, None), (2, None), (1, None), (7, None)])

>>> mio_diccionario = {
...     1: 'uno',
...     2: 'dos',
...     3: 'tres',
...     4: 'cuatro',
...     5: 'cinco',
...     6: 'seis',
... }
>>> my_dict.update(mio_diccionario)
{6: 'seis', 5: 'cinco', 4: 'cuatro', 3: 'tres', 2: 'dos', 1: 'uno', 7: None}

Idiomatic use of lists and dictionaries

It's important that you strive to make your code as Python-like as possible. Such code will often be more readable, easier to maintain, and have fewer bugs. Sometimes, it's more efficient, too. With that in mind, here are some idiomatic ways to deal with lists and dictionaries.

########
# Lists
########

>>> fruits = ['apple', 'pear', 'strawberry']
['apple', 'pear', 'strawberry']

# for loops
>>> for fruit in fruits:
...     print(fruit)
apple
pear
strawberry

>>> first, second, third = fruits
>>> first
'apple'
>>> second
'pear'
>>> third
'strawberry'

>>> if 'pear' in fruits:
...     print("'pear' is in the fruit list.")
'pear' is in the fruit list.

>>> greens = ['salad', 'broccoli', 'celery']

>>> plant_foods = greens + fruits
>>> plant_foods
['salad', 'broccoli', 'celery', 'apple', 'pear', 'strawberry']

>>> plant_foods * 2
['salad', 'broccoli', 'celery', 'apple', 'pear', 'strawberry', 'salad', 'broccoli', 'celery', 'apple', 'pear', 'strawberry']
########
# Dicts
########

>>> person = {
...     'name': 'Alice',
...     'age': 30,
... }
>>> person_continued = {
...     'profession': 'doctor',
...     'language': 'English',
... }

>>> person = person | person_continued
>>> person
{'name': 'Alice', 'age': 30, 'profession': 'doctor', 'language': 'English'}

>>> if 'age' in person:
...     print("Age is available")
Age is available

>>> for key in person:
...     print(key)
name
age
profession
language

>>> for value in person.values():
...     print(value)
Alice
30
doctor
English

>>> for key, value in person.items():
...     print(f"{key}: {value}")
name: Alice
age: 30
profession: doctor
language: English

>>> employees = {
...     'Alice': {'age': 30, 'language': 'English'},
...     'Jurgen': {'age': 27, 'language': 'Dutch'}
... }
>>> employees
{'Alice': {'age': 30, 'language': 'English'}, 'Jurgen': {'age': 27, 'language': 'Dutch'}}

Infinite loops

Some parts of our programs need to run all the time. An infinite loop can create an interactive experience by asking the user for input.

The easiest way to create an infinite loop is with <while True>. The code inside the while loop will keep executing over and over. The condition to end the loop will never happen because True is, by definition, always true.

True infinite loops are not ideal, though. Sometimes, we want to exit the loop. An example is when the user has finished using the program. One brutish way to exit is to end the program's execution at the OS level. You can exit the programs we wrote by typing Control-C. Let's try it:

% python3 Project3.py
What will you do next?
[P]rint - [A]dd - [R]emove - [Q]uit
^CTraceback (most recent call last):
  File "~/Project3.py", line 107, in <module>
    main()
  File "~/Project3.py", line 94, in main
    action = get_user_input()
             ^^^^^^^^^^^^^^^^
  File "~/Project3.py", line 9, in get_user_input
    action = input("[P]rint - [A]dd - [R]emove - [Q]uit\n").strip().lower()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

%

As you can see, it's ugly. The CTRL-C I typed is the "^C" before "Traceback."

We don't want a bunch of gibberish on our screens every time we exit a program. We can exit an infinite while loop with a break statement.

while True:
    command = input("input")
    if command == 'QUIT':
        break
    print(f"You typed {command}")

Another useful statement is continue. <continue> ends the current iteration and skips to the next one.

>>> while True:
...     n = int(input("Give a number: "))
...     if n == 0:
...         print("zero")
...         continue
...     n = n % 3
...     print(n)

Give a number: 4
1
Give a number: 1
1
Give a number: 3
0
Give a number: 0
zero
Give a number: 2
2

Dispatching

Dispatching is when you redirect your program based on conditions or inputs.

This lets you handle various scenarios (e.g., answering user commands.) When a user enters some input, the program decodes it and decides what to do.

We saw two types: conditional dispatching and using dispatch tables.

The first technique involves if-elif-else code. Each possible command has its branch. The program goes through each branch in order. It stops when it finds the right one and executes the corresponding code.

Here is a little guessing game to illustrate conditional dispatching. It is a very easy game because you have as many tries as you want, and the results are fixed.

print("GUESSING GAME... What surprise will you receive?")

while True:
    command = input("GUESS A LETTER: [A] [B] [C] or [Q]uit: ")
    if command == "Q":
        break
    elif command == "A":
        print("You receive a coffee. You feel refreshed.")
    elif command == "B":
        print("You receive a worthless trinket. You try your best to be enthusiastic but fail miserably.")
    elif command == "C":
        print("You just got mugged. No money left for rent.")
    else:
        print("This is not a valid choice. Try again...")

Dispatch tables involve creating a table of functions. Each of the program's possible actions has a spot in the table. You index into the table by using the command. This technique is less natural but remains sound for long lists of possible actions.

The code below is a very basic calculator, showing table dispatching. As an exercise, implement get_user_input(). It should accept input of the form "x <op> y", where <op> is a key in the op_table.

def addition(x, y):
    return x + y

def substraction(x, y):
    return x - y

def multiplication(x, y):
    return x * y

def division(x, y):
    return x / y

def int_division(x, y):
    return x // y

def modulo(x, y):
    return x % y

op_table = {
    '+': addition,
    '-': substraction,
    '*': multiplication,
    '/': division,
    '//': int_division,
    '%': modulo,
}

while True:
    op, x, y = get_user_input()
    op_table[op](x, y)

Exercises

  1. Write a program allowing users to add or remove items from a list. Start with an empty list. Don't forget to check whether the item you want to remove is in the list!

    1. Change the program to be case-insensitive. The program should treat "TOMATOES," "tOmAtOeS," and "tomatoes" the same.
  2. Add options to sort the grocery list in our grocery list program.

    1. in alphabetical order of groceries

    2. by quantity

  3. The grocery list program allows users to specify no quantity when entering a grocery. In this case, use a default of 1.

  4. Limit the grocery list to 10 items. If the user tries to add more, print an error message.

  5. Rework the program and make it multi-user. Users should be able to create their grocery lists. The program should allow them to switch between users.

  6. Rework the program to handle a pantry and recipes. A pantry is a dictionary containing key-value pairs, like grocery lists. The key is a grocery string, and the value is a quantity integer. The recipes should have the same structure. Generate a grocery list by comparing a recipe to a pantry. The grocery list should contain the items necessary for the recipe that are not in the pantry.

  7. Change the data structure of pantries and grocery lists to include expiration dates. Adapt the code to work with these new data structures.

  8. Fizzbuzz. A classic program. Write a function fizzbuzz, taking a positive integer as an argument. Go through each integer from 1 to the argument. If the integer is a multiple of 2, print "fizz"; if it is a multiple of 3, print "fuzz"; if it is a multiple of both 2 and 3, print "fizzbuzz". If it is neither divisible by 2 or 3, print the number. For fizzbuzz(10), the output would be 1 fizz buzz fizz 5 fizzbuzz 7 fizz buzz fizz.

I hope you learned something about lists and dictionaries today. I’d be happy to answer your questions in the comments below. Feel free to tell me what you thought of the post!

« Previous article

Next article »

0
Subscribe to my newsletter

Read articles from Had Will directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Had Will
Had Will