BYU logo Computer Science

To start this guide, download this zip file.

Lists of Tuples

Any time you store data in a list you can work with that data using the list patterns we have learned:

  • map — create a new list that has the same number of items as the original list, with each item in the original list mapped to an item in the new list
  • filter — create a new list that has only some of the items of the original list
  • select — choose a single value from the list
  • accumulate — add or subtract the values in a list

We will show you a few examples of how to do this with lists of tuples.

Scaling a recipe

Write a program that scales a recipe, for example doubles or triples it. First, the person running the program inputs a list of ingredients. For each ingredient, they should provide the item, the quantity, and the units (for example, cups or tsps). Then the person enters a scaling factor (for example, 2 or 3). The program then prints otu the recipe scaled to that factor, with all quantities rounded to 1 decimal place. For example:

What ingredients are in your recipe?
Ingredient: flour
Quantity: 2
Unit: cups
Ingredient: salt
Quantity: 1
Unit: tsps
Ingredient: baking soda
Quantity: 1
Unit: tsps
Ingredient: water
Quantity: 1
Unit: cups
Ingredient:
Scaling factor: 2.5
New Recipe:
  5.0 (cups) flour
  2.5 (tsps) salt
  2.5 (tsps) baking soda
  2.5 (cups) water

Planning

See if you can write this program with a friend. You have starter code in the zip file above, in the file called recipe.py:

def main():
    # Write code here
    pass


if __name__ == '__main__':
    main()

Start by decomposing the problem into functions! What are the functions you would use in main()?

work with a friend to solve this problem

Here is one way to design the program:

get ingredients, get the factor, scale the recipe, then print the recipe

Notice that the black arrows show return values being stored in variables, and blue arrows show those variables being used as arguments in other functions.

We can put this into code:

def main():
    recipe = get_ingredients()
    factor = get_factor()
    recipe = scale_recipe(recipe, factor)
    print_recipe(recipe)

Getting ingredients

To get the ingredients, we need to loop forever, getting one ingredient at a time, until an ingredient is None:

def get_ingredients() -> list[tuple[str, float, str]]:
    print('What ingredients are in your recipe?')
    recipe = []
    while True:
        item = get_ingredient()
        if item is None:
            break
        recipe.append(item)
    return recipe

This follows the same pattern we saw when doing practice with tuples.

Likewise, to get an individual ingredient we input for each of the pieces of information we need, and return either None or a tuple:

def get_ingredient() -> tuple[str, float, str] | None:
    ingredient = input('Ingredient: ')
    if not ingredient:
        return None
    quantity = float(input('Quantity: '))
    unit = input('Unit: ')
    return ingredient, quantity, unit

Getting a factor

To get a factor, we need to ask the person to enter a number. This could be a floating point number, like 2.5! Remember, input() can only return a string, so to convert an integer into a float, we can do this:

def get_factor() -> float:
    response = input('Scaling factor: ')
    factor = float(response)
    return factor

Alternatively, we can do this all in one line:

def get_factor() -> float:
    return float(input('Scaling factor: '))

Scaling the recipe

OK, now comes the part where we need to scale the recipe by the factor. So we need to write this function:

def scale_recipe(recipe: list[tuple[str, float, str]], factor: float) -> list[tuple[str, float, str]]:

It takes a recipe, which is a list of ingredients, and a factor, which is a float. Here is an example of what the recipe could have:

recipe = [('flour', 2.0, 'cups'), ('salt', 1.0, 'tsps'), ('baking soda', 1.0, 'tsps'), ('water', 1.0, 'cups')

To scale the recipe, we need to map each ingredient to a new ingredient that has, for example, twice as much. The amount we use for the mapping is in the factor variable:

new_item = (ingredient, quantity * factor, unit)

Here is the complete function:

def scale_recipe(recipe: list[tuple[str, float, str]], factor: float) -> list[tuple[str, float, str]]:
    new_recipe = []
    for ingredient, quantity, unit in recipe:
        new_item = (ingredient, quantity * factor, unit)
        new_recipe.append(new_item)
    return new_recipe

Printing the recipe

The last step is to print the recipe:

def print_recipe(recipe: list[tuple[str, float, str]]):
    print('New Recipe:')
    for ingredient, quantity, unit in recipe:
        print(f'  {quantity} ({unit}) {ingredient}')

You should be able to run the program and see it working.

Fishing

Write a program that allows a person to input information on a series of fish they have caught. For each catch, the person should provide the place, type, and size (in inches) of the fish. Then allow the person to specify a type of fish to report on. For that type, print a report indicating:

  • the total number of fish of that type
  • the catch with the largest fish of that type

For example:

Place: Utah Lake
Type of fish: Trout
Size (inches): 6
Place: Lake Powell
Type of fish: Bass
Size (inches): 12
Place: Flaming Gorge
Type of fish: Bass
Size (inches): 18
Place: Strawberry
Type of fish: Trout
Size (inches): 13
Place: Bear Lake
Type of fish: Trout
Size (inches): 15
Place:
Fish type: Trout
Total # of Trout: 3
Best Trout catch: 15.0 inches at Bear lake

Planning

See if you can write this program with a friend. You have starter code in the zip file above, in the file called fishing.py:

def main():
    # Write code here
    pass


if __name__ == '__main__':
    main()

Start by decomposing the problem into functions! What are the functions you would use in main()?

work with a friend to solve this problem

Here is one way to design the program:

get the catches, get the fish type, filter the fish, print a report

Notice that the black arrows show return values being stored in variables, and blue arrows show those variables being used as arguments in other functions.

Since this is a more complicated program, let’s work one step at a time, and just get the catches first:

def main():
    catches = get_catches()
    print(catches)

Getting the catches

Getting the catches is just like the previous problems we have worked on:

def get_catches() -> list[tuple[str, str, float]]:
    catches = []
    while True:
        catch = get_catch()
        if catch is None:
            break
        catches.append(catch)
    return catches
  • Start with an empty list of fish
  • Loop forever
    • Get info on a fish caught
    • If the catch is None, break
    • Otherwise, append the fish to the list of catches
  • Return the list of fish caught

To get info on an individual fish that was caught:

def get_catch() -> tuple[str, str, float] | None:
    place = input('Place: ')
    if place == '':
        return None
    fish = input('Type of fish: ')
    size = float(input('Size (inches): '))
    return place, fish, size

We should be able to run this program and enter a few fish:

Place: Utah Lake
Type of fish: Trout
Size (inches): 6
Place: Lake Powell
Type of fish: Bass
Size (inches): 12

Then you will see the program print out the list of tuples:

[('Utah Lake', 'Trout', 6.0), ('Lake Powell', 'Bass', 12.0)]

Great!

Filtering the fish

The next step in our diagram is to get a fish type and then filter the list of catches with that type. By filter we mean return a new list of fish that has only fish of that type.

First, modify main():

def main():
    catches = get_catches()
    fish_type = get_fish_type()
    catches_of_fish = filter_to_type(catches, fish_type)
    print(catches_of_fish)

This will let us do the next step and print out the filtered list of fish, to be sure that next step is working.

Now we can write get_fish_type():

def get_fish_type() -> str:
    return input('Fish type: ')

This is probably the easiest function you will write all semester. :-)

We can also write filter_to_type():

def filter_to_type(catches: list[tuple[str, str, float]], fish_type: str) -> list[tuple[str, str, float]]:
    keepers = []
    for place, fish, size in catches:
        if fish == fish_type:
            keepers.append((place, fish, size))
    return keepers

We chose to use keepers as the variable here because it does a good job of showing what filter does — it “keeps” some of the items in the original list, but throws the rest away.

We loop through all the catches, unpacking them as we go. Then only if fish == fish_type do we append the fish to the keepers.

If you run this, and enter the following:

Place: Utah Lake
Type of fish: Trout
Size (inches): 6
Place: Lake Powell
Type of fish: Bass
Size (inches): 12
Place:
Fish type: Trout

Then the program should print:

[('Utah Lake', 'Trout', 6.0)]

Notice how the filter works by creating a new list that keeps only the tuples we want.

Printing a report

Modify main() one more time:

def main():
    catches = get_catches()
    fish_type = get_fish_type()
    catches_of_fish = filter_to_type(catches, fish_type)
    print_report(catches_of_fish, fish_type)

Now we have a complete program. We just need to write print_report():

def print_report(catches: list[tuple[str, str, float]], fish_type: str):
    place, fish, size = find_max(catches)

    print(f'Total # of {fish_type}: {len(catches)}')
    print(f'Best {fish} catch: {size} inches at {place}')

We want to have a function called find_max() that returns a tuple with all the information about the largest fish caught. This is an example of a select function. We want to select one of the tuples out of the list of tuples.

def find_max(catches: list[tuple[str, str, float]]) -> tuple[str, str, float]:
    best_fish = None
    best_place = None
    best_size = None
    for place, fish, size in catches:
        if best_size is None or size > best_size:
            best_size = size
            best_fish = fish
            best_place = place
    return best_place, best_fish, best_size
  • Start by creating three variables, one for each part of the tuple.
  • Initialize each of these variables to None to represent the fact that the largest fish hasn’t been found yet.
  • Loop through all of the fish, using unpacking
    • if the best size is None or the current size is bigger than the best size, then change all of the three variables to match this fish
  • Return a tuple with information about the biggest fish

Now you can run the program one more time and it should print out the report we need!