Closure#

Syllabus week 8: Dictionaries and tuples#

  • Dictionary, a collection of key-value pairs

  • Creating dictionaries: enclosing key-value pairs in curly brackets

  • Keyword in for dictionaries operates on keys, not values

  • Traversing a dictionary

  • Adding key-value pairs to the dictionary

  • Dictionaries are mutable

  • Tuple, an immutable sequence of values

  • Creating tuple with or without parenthesis

  • Unpacking, i.e a, b = my_function()

  • Using tuple as a return value

Advanced#

Note about the material in Advanced section. The advanced material contains more some additional topics related to the weeks’s content. You are not required to read this material, and none of the exercises or exam questions will rely on this material.

Advanced 8.1: Nested Dictionaries#

We have mentioned that values in dictionaries may be of any type, including other dictionaries. Check for example a following nested dictionary.

person_data = {
    "Jakob": {
        "age": 25,
        "height": 175,
    },
    "Josefine": {
        "age": 30,
        "height": 165,
    },
    "Morten": {
        "age": 37,
        "height": 189,
    },
}
print(person_data["Jakob"]["age"])

Copy and run the code below which illustrates how to create and traverse a nested dictionary. Can you explain what key, nested_key and value refers to?

nested_weather_data = {"Monday": {"temperature": 21, "wind_speed": 4.2},
                       "Tuesday": {"temperature": 18, "wind_speed": 3.2},
                       "Wednesday": {"temperature": 15, "wind_speed": 1.8},
                       "Thursday": {"temperature": 17, "wind_speed": 2.6},
                       "Friday": {"temperature": 25, "wind_speed": 3.9}}

print(nested_weather_data)
print(nested_weather_data["Monday"])
print(nested_weather_data["Monday"]["temperature"])

for key in nested_weather_data:
    print(key, end=" ")
    nested_dictionary = nested_weather_data[key]
    for nested_key in nested_dictionary:
        value = nested_dictionary[nested_key]
        print(nested_key, value, end=" ")
    print("")

Advanced 8.2: Efficiency of Dictionaries#

Dictionaries have a very efficient way of storing and retrieving elements. In the code below, we make a list with a million elements and a dictionary with a million elements. We then measure how long it takes to find a certain element in each data structure.

For this we use a time module that has a function time() that returns the current time in seconds.

import time

my_dict = {}
my_list = []
for i in range(1000000):
    my_dict[str(i)] = i
    my_list.append(str(i))

t = time.time()
search_list = "1000000" in my_list
time_list = time.time() - t
print("Time taken using list:", time_list * 1000, "ms")

t = time.time()
search_dict = "1000000" in my_dict
time_dict = time.time() - t
print("Time taken using dictionary:", time_dict * 1000, "ms")

print("Dictionary is", time_list / time_dict, "times faster than list")

When searching a list, Python has to check each element. On the other hand, dictionary lookups uses a hash function to find the element, which is much faster. The explanation of this is a bit technical but the main point is that dictionaries are much faster than lists for looking up elements, especially when the number of elements is large.

Advanced 8.3: Dictionary Methods#

Run the following code.

book = {"Title": "The Hitchhiker's Guide to the Galaxy", "Author": "Douglas Adams", "Genre": "Science fiction"}

print(book.keys())
print(book.values()) 
print(book.items())

The keys(), values(), and items() are built-in methods operating on dictionaries and giving access to the keys, values, and key-value pairs. These methods return a dictionary view object, which is a data type that behaves differently than date types you’ve seen so far. You can iterate over the view objects, but you cannot access individual elements by index. If you need to access individual elements, you can convert the view object to a list.

Check the code below to see how you can iterate over the keys, values, and key-value pairs of a dictionary.

grades = {"Josefine": 12, "Jakob": 10, "Alice": 7, "Morten": 10}

for key in grades.keys():
    print(key)

for value in grades.values():
    print(value)

for key, value in grades.items():
    print(key, value)

In the last example, since items() returns each key-value pair as a tuple, you can use variable unpacking. This enables you to have two iteration variables, one which loops over the keys and one which loops over the values.

Advanced 8.4: Constructor dict#

Run the following code.

numbers = dict(x=1, y=2, z=3)
print(numbers)

fruits = dict([("first", "apple"), ("second", "orange"), ("third", "banana")])
print(fruits)

sequences = dict(first_seq=dict(a = [1, 2, 3]), second_seq=dict(b = [4, 5, 6]), third_seq=dict(c = [7, 8, 9]))
print(sequences)

The constructor dict can be used to create a dictionary. The key-value pairs may be given as keyword arguments, or as a list of tuples. This is just another way to create a dictionary, which you might see in other people’s code.

Advanced 8.5: Unpacking Using Single Double Asterisk#

You can unpacks a sequence into individual positional arguments when passed to a function. See the example below.

def example_func(a, b, c):
    print(a, b, c)

my_input = (1, 2, 3)
example_func(*my_input)  

If you unpack a dictionary, this passes key-value pairs as keyword arguments to a function.

def example_func(a, b, c):
    print(a, b, c)

my_dict = {'b': 2, 'c': 3, 'a': 1}
example_func(**my_dict)  

Advanced 8.6: Variable Length Arguments#

You can define functions that take a variable number of arguments. For example, check the code below.

def example_variable_length_args(a, b, c, *args):
    print(a, b, c)
    print(args)
    print(type(args))

example_variable_length_args(1, 2, 3, "First", "Second", "Third")