Computer Science

# 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()`?

Here is one way to design the program:

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():
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():
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():
response = input('Scaling factor: ')
factor = float(response)
return factor``````

Alternatively, we can do this all in one line:

``````def get_factor():
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, factor):``

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, 'cups'), ('salt', 1, 'tsps'), ('baking soda', 1, 'tsps'), ('water', 1, '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, factor):
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):
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()`?

Here is one way to design the program:

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():
catches = []
while True:
catch = get_catch()
if catch is None:
break
catches.append(catch)
return catches``````
• 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():
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():
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, fish_type):
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, fish_type):
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):
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!