3  Dictionaries and Functions

In this lesson we will get to know and become experts in:

  1. Functions
  2. Dictionaries
  3. Introduction to numpy

Functions

Functions are essential building blocks to reuse code and to modularize code.

We have already seen and used many built-in functions/methods such as print(), len(), max(), round(), index(), capitalize(), etc..

areas = [11.25, 18.0, 20.0, 10.75, 10.75, 9.5]
print(max(areas))
print(len(areas))
print(round(10.75,1))
print(areas.index(18.0))
20.0
6
10.8
1

But of course we want to define our own functions as well ! As a rule of thumb, if you anticipate needing to repeat the same or very similar code more than once, it may be worth writing a reusable function. Functions can also help make your code more readable by giving a name to a group of Python statements.

For example, we computed the BMI previously as follows:

height = 1.79
weight = 68.7
bmi = weight/height**2
print(bmi)
21.44127836209856

Functions are declared with the def keyword. A function contains a block of code with an optional use of the return keyword:

def compute_bmi(height, weight):
    return weight/height**2

compute_bmi(1.79, 68.7)
21.44127836209856

Each function can have positional arguments and keyword arguments. Keyword arguments are most commonly used to specify default values or optional arguments. For example:

def compute_bmi(height, weight, ndigits=2):
    return round(weight/height**2, ndigits)

print(compute_bmi(1.79, 68.7))
print(compute_bmi(1.79, 68.7,4))
21.44
21.4413

Multiple Return Values

are easily possible in python:

def compute_bmi(height, weight, ndigits=2):
    bmi = round(weight/height**2, ndigits)
    #https://www.cdc.gov/healthyweight/assessing/index.html#:~:text=If%20your%20BMI%20is%20less,falls%20within%20the%20obese%20range.
    if bmi < 18.5:
        status="underweight"
    elif bmi <= 24.9:
        status="healthy"
    elif bmi <= 29.9:
        status="underweight"
    elif bmi >= 30:#note that a simple else would suffice here!
        status="obese"
    return bmi, status

print(compute_bmi(1.79, 68.7))
print(compute_bmi(1.79, 55))
(21.44, 'healthy')
(17.17, 'underweight')

Recall from the previous lab how we

  1. found the largest room,
  2. computed the sum of integers from 1 to 100
#find the maximum area:
areas = [11.25, 18.0, 20.0, 10.75, 10.75, 9.5]
currentMax = areas[0] # initialize to the first area seen

for a in areas:
  if a > currentMax:
    currentMax = a

print("The max is:", currentMax)
The max is: 20.0
#Clever IDB students: Compute the sum from 1 to 100:
Total =0

for i in range(101):#strictly speaking we are adding the first  0 
  Total = Total + i
  #Total += i

print(Total)

Tasks

Write your own function

  1. to find the min and max of a list
  2. to compute the Gauss sum with defaukt values \(m=1, n=100\)

\[ \sum_{i=m}^{n}{i} \]

Namespaces and Scope

Functions seem straightforward. But one of the more confusing aspects in the beginning is the concept that we can have multiple instances of the same variable!

Functions can access variables created inside the function as well as those outside the function in higher (or even global) scopes. An alternative and more descriptive name describing a variable scope in Python is a namespace. Any variables that are assigned within a function by default are assigned to the local namespace. The local namespace is created when the function is called and is immediately populated by the function’s arguments. After the function is finished, the local namespace is destroyed.

Examples:

height = 1.79
weight = 68.7
bmi = weight/height**2
#print("height, weight, bmi OUTSIDE the function:",height, weight,bmi)

def compute_bmi(h, w):
    height = h
    weight = w
    bmi = round(weight/height**2,2)
    status="healthy"
    print("height, weight, bmi INSIDE the function:",height, weight,bmi)
    print("status:", status)
    return bmi

compute_bmi(1.55, 50)

print("height, weight, bmi OUTSIDE the function:",height, weight,bmi)
#print(status)
height, weight, bmi INSIDE the function: 1.55 50 20.81
status: healthy
height, weight, bmi OUTSIDE the function: 1.79 68.7 21.44127836209856

Dictionaries

A dictionary is basically a lookup table. It stores a collection of key-value pairs, where key and value are Python objects. Each key is associated with a value so that a value can be conveniently retrieved, inserted, modified, or deleted given a particular key.

The dictionary or dict may be the most important built-in Python data structure. In other programming languages, dictionaries are sometimes called hash maps or associative arrays.

#This was the house defined as a list of lists:
house = [['hallway', 11.25],
 ['kitchen', 18.0],
 ['living room', 20.0],
 ['bedroom', 10.75],
 ['bathroom', 9.5]]

#Remember all the disadvantages of accessing elements

#Better as a lookup table:
house = {'hallway': 11.25,
    'kitchen': 18.0,
    'living room': 20.0,
    'bedroom': 10.75,
    'bathroom': 9.5}
europe = {'spain':'madrid', 'france' : 'paris'}
print(europe["spain"])
print("france" in europe)
print("paris" in europe)#only checks the keys!
europe["germany"] = "berlin"
print(europe.keys())
print(europe.values())
madrid
True
False
dict_keys(['spain', 'france', 'germany'])
dict_values(['madrid', 'paris', 'berlin'])

Dictionaries from lists

How would we convert two lists into a key: value pair dictionary?

Method 1: using zip

rooms=['hallway', 'kitchen', 'living room', 'bedroom', 'bathroom']
areas=[11.25, 18.0, 20.0, 10.75, 9.5]
#create list of tuples
list(zip(rooms, areas))
[('hallway', 11.25),
 ('kitchen', 18.0),
 ('living room', 20.0),
 ('bedroom', 10.75),
 ('bathroom', 9.5)]
dict(zip(rooms, areas))
{'hallway': 11.25,
 'kitchen': 18.0,
 'living room': 20.0,
 'bedroom': 10.75,
 'bathroom': 9.5}

If you need to iterate over both the keys and values, you can use the items method to iterate over the keys and values as 2-tuples:

#print(list(europe.items()))

for country, capital in europe.items():
    print(capital, "is the capital of", country)
madrid is the capital of spain
paris is the capital of france
berlin is the capital of germany

Note: You can use integers as keys as well. However -unlike in lists- one should not think of them as positional indices!

#Assume you have a basement:
house[0] = 21.5
house
{'hallway': 11.25,
 'kitchen': 18.0,
 'living room': 20.0,
 'bedroom': 10.75,
 'bathroom': 9.5,
 0: 21.5}
#And there is a difference between the string and the integer index!
house["0"] = 30.5
house
{'hallway': 11.25,
 'kitchen': 18.0,
 'living room': 20.0,
 'bedroom': 10.75,
 'bathroom': 9.5,
 0: 21.5}

Categorize a list of words by their first letters as a dictionary of lists:

words = ["apple", "bat", "bar", "atom", "book"]

by_letter = {}

for word in words:
     letter = word[0]
     if letter not in by_letter:
        by_letter[letter] = [word]
     else:
         by_letter[letter].append(word)
{'a': ['apple', 'atom'], 'b': ['bat', 'bar', 'book']}

Tasks

  1. Find the maximum of the areas of the houses
  2. Remove the two last entries.
  3. Write a function named word_count that takes a string as input and returns a dictionary with each word in the string as a key and the number of times it appears as the value.

Introduction to numpy

NumPy, short for Numerical Python, is one of the most important foundational packages for numerical computing in Python.

  1. Vectorized, fast mathematical operations.
  2. Key features of NumPy is its N-dimensional array object, or ndarray
height = [1.79, 1.85, 1.95, 1.55]
weight = [70, 80, 85, 65]

#bmi = weight/height**2
import numpy as np

height = np.array([1.79, 1.85, 1.95, 1.55])
weight = np.array([70, 80, 85, 65])

bmi = weight/height**2
np.round(bmi,2)
array([21.84700852, 23.37472608, 22.35371466, 27.05515088])