Dictionaries 2 - operators

Download exercise zip

Browse online files

There are several operators to manipulate dictionaries:

Operator

Syntax

Return

Description

length

len(dict)

int

Retorn the number of keys

reading

dict[key]

obj

Return the value associated to the key

writing

dict[key] = value

Adds or modify the value associated to the key

deletion

del dict[key]

Removes the key/value couple

membership

obj in dict

bool

Return True if the key obj is present in dict

equality

==,!=

bool

Checks whether two dictionaries are equal or different

What to do

  1. Unzip exercises zip in a folder, you should obtain something like this:

dictionaries
    dictionaries1.ipynb
    dictionaries1-sol.ipynb
    dictionaries2.ipynb
    dictionaries2-sol.ipynb
    dictionaries3.ipynb
    dictionaries3-sol.ipynb
    dictionaries4.ipynb
    dictionaries4-sol.ipynb
    dictionaries5-chal.ipynb
    jupman.py

WARNING: to correctly visualize the notebook, it MUST be in an unzipped folder !

  1. open Jupyter Notebook from that folder. Two things should open, first a console and then a browser. The browser should show a file list: navigate the list and open the notebook dictionaries2.ipynb

  2. Go on reading the exercises file, sometimes you will find paragraphs marked Exercises which will ask to write Python commands in the following cells.

Shortcut keys:

  • to execute Python code inside a Jupyter cell, press Control + Enter

  • to execute Python code inside a Jupyter cell AND select next cell, press Shift + Enter

  • to execute Python code inside a Jupyter cell AND a create a new cell aftwerwards, press Alt + Enter

  • If the notebooks look stuck, try to select Kernel -> Restart

len

We can obtain the number of key/value associations in a dictionary by using the function len:

[2]:
len({'a':5,
     'b':9,
     'c':7
})
[2]:
3
[3]:
len({3:8,
     1:3
})
[3]:
2
[4]:
len({})
[4]:
0

QUESTION: Look at the following expressions, and for each try guessing the result (or if it gives an error):

  1. len(dict())
    
  2. len({'a':{}})
    
  3. len({(1,2):{3},(4,5):{6},(7,8):{9}})
    
  4. len({1:2,1:2,2:4,2:4,3:6,3:6})
    
  5. len({1:2,',':3,',':4,})
    
  6. len(len({3:4,5:6}))
    

Reading a value

At the end of dictionaries definition, it is reported:

Given a key, we can find the corresponding value very fast

How can we specify the key to search? It’s sufficient to use square brackets [ ], a bit like we already did for lists:

[5]:

furniture = { 'chair' : 'a piece of furniture to sit on', 'cupboard' : 'a cabinet for storage', 'lamp' : 'a device to provide illumination' }
[6]:
furniture['chair']
[6]:
'a piece of furniture to sit on'
[7]:
furniture['lamp']
[7]:
'a device to provide illumination'

WARNING: What we put in square parenthesis must be a key present in the dictionary

If we put keys which are not present, we will get an error:

>>> furniture['armchair']

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-19-ee891f51417b> in <module>
----> 1 furniture['armchair']

KeyError: 'armchair'

Fast disorder

Whenever we give a key to Python, how fast is it in getting the corresponding value? Very fast, so much so the speed does not depend on the dictionary dimension. Whether it is small or huge, given a key it will always find the associated value in about the same time.

When we hold a dictionary in real life, we typically have an item to search for and we turn pages until we get what we’re looking for: the fact items are sorted allows us to rapidly find the item.

We might expect the same also in Python, but if we look at the definition we find a notable difference:

Dictionaries are mutable containers which allow us to rapidly associate elements called keys to some values

Keys are immutable, don’t have order and there cannot be duplicates Values can be duplicated

If keys are not ordered, how can Python get the values so fast? The speed stems from the way Python memorizes keys, which is based on hashes, similarly for what happens with sets. The downside is we can only immutable objects as keys.

QUESTION: If we wanted to print the value 'a device to provide illumination' we see at the bottom of the dictionary, without knowing it corresponds to lamp, would it make sense to write something like this?

furniture = {'chair':'a piece of furniture to sit on',
             'cupboard':'a cabinet for storage',
             'lamp': 'a device to provide illumination'
}

print( furniture[2] )
Show answer

QUESTION: Look at the following expressions, and for each try guessing which result it produces (or if it gives an error):

kabbalah = {
    1 : 'Progress',
    3 : 'Love',
    5 : 'Creation'
}
  • kabbalah[0]
    
  • kabbalah[1]
    
  • kabbalah[2]
    
  • kabbalah[3]
    
  • kabbalah[4]
    
  • kabbalah[5]
    
  • kabbalah[-1]
    
Show answer

QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):

  1. {'a':4,'b':5}('a')
    
  2. {1:2,2:3,3:4}[2]
    
  3. {'a':1,'b':2}['c']
    
  4. {'a':1,'b':2}[a]
    
  5. {'a':1,'b':2}[1]
    
  6. {'a':1,'b':2,'c':3}['c']
    
  7. {'a':1,'b':2,'c':3}[len(['a','b','c'])]
    
  8. {(3,4):(1,2)}[(1,2)]
    
  9. {(1,2):(3,4)}[(1,2)]
    
  10. {[1,2]:[3,4]}[[1,2]]
    
  11. {'a','b','c'}['a']
    
  12. {'a:b','c:d'}['c']
    
  13. {'a':4,'b':5}{'a'}
    
  14. d1 = {'a':'b'}
    d2 = {'b':'c'}
    print(d1[d2['c']])
    
  15. d1 = {'a':'b'}
    d2 = {'b':'c'}
    print(d2[d1['a']])
    
  16. {}[]
    
  17. {[]:3}[[]]
    
  18. {1:7}['1']
    
  19. {'':7}"[]"
    
  20. {'':7}[""]
    
  21. {"":7}['']
    
  22. {'"':()}['']
    
  23. {():7}[()]
    
  24. {(()):7}[()]
    
  25. {(()):7}[((),)]
    

Exercise - z7

✪ Given a dictionary d1 with keys 'b' and 'c' and integer values, create a dictionary d2 containing the key 'z' and associate to it the sum of values of keys from d1

  • your code must work for any d1 with keys 'b' and 'c'

Example - given:

d1 = {'a':6, 'b':2,'c':5}

After your code, it must result:

>>> print(d2)
{'z': 7}
Show solution
[8]:

d1 = {'a':6, 'b':2,'c':5} # write here

Writing in the dictionary

Can we write in a dictionary?

Dictionaries are mutable containers which allow us to rapidly associate elements called keys to some values

The definition talks about mutability, so we are allowed to modify dictionaries after creation.

Dictionaries are collections of key/value couples, and among the possible modifications we find:

  1. adding a key/value couple

  2. associate an existing key to a different value

  3. remove a key/value couple

Writing - adding key/value

Suppose we created our dictionary furniture:

[9]:

furniture = { 'chair' : 'a piece of furniture to sit on', 'cupboard' : 'a cabinet for storage', 'lamp' : 'a device to provide illumination' }

and afterwards we want to add a definition for 'armchair'. We can reuse the variable furniture followed by square brackets with inside the key we want to add ['armchair'] and after the brackets we will put an equality sign =

[10]:
furniture['armchair'] = 'a chair with armrests'

Note Jupyter didn’t show results, because the previous operation is an assignment command (only expressions generate results).

But something did actually happen in memory, we can check it by furniture:

[11]:
furniture
[11]:
{'chair': 'a piece of furniture to sit on',
 'cupboard': 'a cabinet for storage',
 'lamp': 'a device to provide illumination',
 'armchair': 'a chair with armrests'}

Note the dictionary associated to the variable furniture was MODIFIED with the addition of 'armchair'.

When we add a key/value couple, we can use heterogenous types:

[12]:
trashcan = {
    'bla' : 3,
     4    : 'boh',
    (7,9) : ['gar','bage']
}
[13]:
trashcan[5.0] = 'a float'
[14]:
trashcan
[14]:
{'bla': 3, 4: 'boh', (7, 9): ['gar', 'bage'], 5.0: 'a float'}

We are subject to the same constraints on keys we have during the creation, so we can only use immutable keys. If we try inserting a mutable type, for example a list, we will get an error:

>>> trashcan[ ['some', 'list']  ] = 8

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-51-195ac9c21bcd> in <module>
----> 1 trashcan[ ['some', 'list']  ] = 8

TypeError: unhashable type: 'list'

QUESTION: Look at the following expressions, and for each try guessing the result (or if gives an error):

  1. d = {1:'a'}
    d[2] = 'a'
    print(d)
    
  2. d = {}
    print(len(d))
    d['a'] = 'b'
    print(len(d))
    
  3. d1 = {'a':3, 'b':4}
    diz2 = diz1
    diz1['a'] = 5
    print(diz1)
    print(diz2)
    
  4. diz1 = {'a':3, 'b':4}
    diz2 = dict(diz1)
    diz1['a'] = 5
    print(diz1)
    print(diz2)
    
  5. la = ['a','c']
    diz = {'a':3,
           'b':4,
           'c':5}
    diz['d'] = diz[la[0]] + diz[la[1]]
    print(diz)
    
  6. diz = {}
    diz[()]: ''
    diz[('a',)]: 'A'
    diz[('a','b')]: 'AB'
    print(diz)
    
  7. la = [5,8,6,9]
    diz = {}
    diz[la[0]]=la[2]
    diz[la[2]]=la[0]
    print(diz)
    
  8. diz = {}
    diz[(4,5,6)[2]] = 'c'
    diz[(4,5,6)[1]] = 'b'
    diz[(4,5,6)[0]] = 'a'
    print(diz)
    
  9. diz1 = {
        'a' : 'x',
        'b' : 'x',
        'c' : 'y',
        'd' : 'y',
    }
    
    diz2 = {}
    diz2[diz1['a']] = 'a'
    diz2[diz1['b']] = 'b'
    diz2[diz1['c']] = 'c'
    diz2[diz1['d']] = 'd'
    print(diz2)
    

Writing - reassociate a key

Let’s suppose to change the definition of a lamp:

[15]:
furniture = {'chair':'a piece of furniture to sit on',
             'cupboard':'a cabinet for storage',
             'lamp': 'a device to provide illumination'
}
[16]:
furniture['lamp'] = 'a device to provide visible light from electric current'
[17]:
furniture
[17]:
{'chair': 'a piece of furniture to sit on',
 'cupboard': 'a cabinet for storage',
 'lamp': 'a device to provide visible light from electric current'}

Exercise - workshop

✪ MODIFY the dictionary workshop:

  1. set the 'bolts' key value equal to the value of the 'pincers' key

  2. increment the value of wheels key of 1

  • your code must work with any number associated to the keys

  • DO NOT create new dictionaries, so no lines beginning with workshop = {

Example - given:

workshop = {'wheels':3,
            'bolts':2,
            'pincers':5}

after your code, you should obtain:

>>> print(workshop)
{'bolts': 5, 'wheels': 4, 'pincers': 5}
Show solution
[18]:
workshop = {'wheels' : 3,
            'bolts'  : 2,
            'pincers': 5}

# write here


QUESTION: Look at the following code fragments expressions, and for each try guessing the result it produces (or if it gives an error):

  1. diz = {'a':'b'}
    diz['a'] = 'a'
    print(diz)
    
  2. diz = {'1':'2'}
    diz[1] = diz[1] + 5   # nasty
    print(diz)
    
  3. diz = {1:2}
    diz[1] = diz[1] + 5
    print(diz)
    
  4. d1 = {1:2}
    d2 = {2:3}
    d1[1] = d2[d1[1]]
    print(d1)
    

Writing - deleting

To remove a key/value couple the special command del is provided. Let’s take a dictionary:

[19]:
kitchen = {
    'pots'  : 3,
    'pans'  : 7,
    'forks' : 20
}

If we want to eliminate the couple pans : 7, we will write del followed by the name of the dictionary and the key to eliminate among square brackets:

[20]:
del kitchen['pans']
[21]:
kitchen
[21]:
{'pots': 3, 'forks': 20}

Trying to delete a non-existemt key will produce an error:

>>> del cucina['crankshaft']

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-34-c0d541348698> in <module>
----> 1 del cucina['crankshaft']

KeyError: 'crankshaft'

QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):

  1. diz = {'a':'b'}
    del diz['b']
    print(diz)
    
  2. diz = {'a':'b', 'c':'d'}
    del diz['a']
    print(diz)
    
  3. diz = {'a':'b', 'c':'d'}
    del diz['a']
    del diz['a']
    print(diz)
    
  4. diz = {'a':'b'}
    new_diz = del diz['a']
    print(diz)
    print(new_diz)
    
  5. diz1 = {'a':'b', 'c':'d'}
    diz2 = diz1
    del diz1['a']
    print(diz1)
    print(diz2)
    
  6. diz1 = {'a':'b', 'c':'d'}
    diz2 = dict(diz1)
    del diz1['a']
    print(diz1)
    print(diz2)
    
  7. diz = {'a':'b'}
    del diz['c']
    print(diz)
    
  8. diz = {'a':'b'}
    diz.del('a')
    print(diz)
    
  9. diz = {'a':'b'}
    diz['a'] = None
    print(diz)
    

Exercise - desktop

Given a dictionary desktop:

desktop = {
    'paper'  :5,
    'pencils':2,
    'pens'   :3
}

write some code which MODIFIES it so that after executing your code, the dictionary appears like this:

>>> print(desktop)
{'pencil sharpeners': 1, 'paper': 5, 'pencils': 2, 'papers': 4}
  • DO NOT write lines which begin with desktop =

Show solution
[22]:

desktop = { 'paper' :5, 'pencils':2, 'pens' :3 } # write here

Exercise - garden

You have a dictionary garden which associates the names of present objects and their quantity. You are given:

  • a list to_remove containing the names of exactly two objects to eliminate

  • a dictionary to_add containing exactly two names of flowers associated to their quantity to add

MODIFY the dictionary garden according to the quantities given in to_remove (deleting the keys) and to_add (increasing the corresponding values)

  • assume that garden always contains the objects given in to_remove and to_add

  • assume that to_add always and only contains tulips and roses

Example - given:

to_remove = ['weeds', 'litter']
to_add = { 'tulips': 4,
           'roses' : 2
}

garden = { 'sunflowers': 3,
           'tulips'    : 7,
           'weeds'     : 10,
           'roses'     : 5,
           'litter'    : 6,
}

after your code, it must result:

>>> print(garden)
{'roses': 7, 'tulips': 11, 'sunflowers': 3}
Show solution
[23]:

to_remove = ['weeds', 'litter'] to_add = { 'tulips': 4, 'roses' : 2 } garden = { 'sunflowers': 3, 'tulips' : 7, 'weeds' : 10, 'roses' : 5, 'litter' : 6, } # write here

Exercise - translations

Given two dictionaries en_it and it_es of English-Italian and Italian-Spanish translations, write some code which MODIFIES a third dictionary en_es by placing translations from English to Spanish

  • assume that en_it always and only contains translations of hello and road

  • assume that it_es always and only contains translations of ciao and strada

  • in the solution, ONLY use the constants 'hello' and 'road', you will take the others you need from the dictionaries

  • DO NOT create a new dictionary - so no lines beginning with en_es = {

Example - given:

en_it = {
    'hello' : 'ciao',
    'road' : 'strada'
}

it_es = {
    'ciao' : 'hola',
    'strada' : 'carretera'
}
en_es = {}

after your code, it must print:

>>> print(en_es)
{'hello': 'hola', 'road': 'carretera'}
Show solution
[24]:

en_it = { 'hello' : 'ciao', 'road' : 'strada' } it_es = { 'ciao' : 'hola', 'strada' : 'carretera' } en_es = {} # write here

Membership with in

We can check whether a key is present in a dictionary by using the operator in:

[25]:
'a' in {'a':5,'b':7}
[25]:
True
[26]:
'b' in {'a':5,'b':7}
[26]:
True
[27]:
'z' in {'a':5,'b':7}
[27]:
False

WARNING: in searches among the keys, not in values!

[28]:
5 in {'a':5,'b':7}
[28]:
False

As always when dealing with keys, we cannot search for a mutable object, like for example lists:

>>> [3,5] in {'a':'c','b':'d'}

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-41-3e3e336117aa> in <module>
----> 1 [3,5] in {'a':'c','b':'d'}

TypeError: unhashable type: 'list'

not in

It is possible to check for non belonging with the not in operator:

[29]:
'z' not in {'a':5,'b':7}
[29]:
True
[30]:
'a' not in {'a':5,'b':7}
[30]:
False

Equivalently, we can use this other form:

[31]:
not 'z' in {'a':5,'b':7}
[31]:
True
[32]:
not 'a' in {'a':5,'b':7}
[32]:
False

QUESTION: Look at the following expressions, and for each try guessing the result (or if it gives an error):

  1. ('a') in {'a':5}
    
  2. ('a','b') in {('a','b'):5}
    
  3. ('a','b',) in {('a','b'):5}
    
  4. ['a','b'] in {('a','b'):5}
    
  5. {3: 'q' in {'q':5}}
    
  6. {'q' not in {'q':0} : 'q' in {'q':0}}
    
  7. {'a' in 'b'}
    
  8. {'a' not in {'b':'a'}}
    
  9. len({'a':6,'b':4}) in {1:2}
    
  10. 'ab' in {('a','b'): 'ab'}
    
  11. None in {}
    
  12. None in {'None':3}
    
  13. None in {None:3}
    
  14. not None in {0:None}
    

Exercise - The Helmsman

The restaurant “The Helmsman” serves a menu with exactly 3 courses each coupled with a side dish. The courses and the side dishes are numbered from 1 to 12. There are many international clients who don’t speak well the local language, so they often simply point a course number. They never point a side dish. Once the order is received, the waiter with a tablet verifies whether the course is ready with the correct side dish. Write some code which given an index of a course shows True if this is in the kitchen coupled with the course, False otherwise.

Example 1 - given:

       # 1         2        3       4         5          6
menu = ['herring','butter','orata','salad',  'salmon',  'potatoes',
       # 7         8          9         10       11         12
        'tuna',   'beans',   'salmon', 'lemon', 'herring', 'salad']


kitchen = {'orata':'salad',
           'salmon':'potatoes',
           'herring':'salad',
           'tuna':'beans'}

order = 1

The program will show False, because there is no association "herring" : "butter" in kitchen

Example 2 - given:

order = 3

the program will show True because there is the association "orata" : "salad" in cambusa

Show solution
[33]:

order = 1 # False #order = 3 # True #order = 5 # True #order = 7 # True #order = 9 # False #order = 11 # True # 1 2 3 4 5 6 menu = ['herring','butter','orata','salad', 'salmon', 'potatoes', # 7 8 9 10 11 12 'tuna', 'beans', 'salmon', 'lemon', 'herring', 'salad'] kitchen = {'orata':'salad', 'salmon':'potatoes', 'herring':'salad', 'tuna':'beans'} # write here

Dictionaries of sequences

So far we almost always associated a single value to keys. What if wanted to associate more? For example, suppose we are in a library and we want to associate users with the books they borrowed. We could represent everything as a dictionary where a list of borrowed books is associated to each customer:

[34]:
loans = {'Marco':  ['Les Misérables', 'Ulysses'],
         'Gloria': ['War and Peace'],
         'Rita':   ['The Shining','Dracula','1984']}

Let’s see how it gets represented in Python Tutor:

[35]:
# WARNING: FOR PYTHON TUTOR TO WORK, REMEMBER TO EXECUTE THIS CELL with Shift+Enter
#          (it's sufficient to execute it only once)

import jupman
[36]:
loans = {'Marco':  ['Les Misérables', 'Ulysses'],
         'Gloria': ['War and Peace'],
         'Rita':   ['The Shining','Dracula','1984']}
jupman.pytut()
[36]:
Python Tutor visualization

If we try writing the expression:

[37]:
loans['Rita']
[37]:
['The Shining', 'Dracula', '1984']

Python shows the corresponding list: for all intents and purposes Python considers loans['Rita'] as if it were a list, and we can use it as such. For example, if we wanted to access the 1-indexed book of the list, we would write [1] after the expression:

[38]:
loans['Rita'][1]
[38]:
'Dracula'

Equivalently, we might also save a pointer to the list by assigning the expression to a variable:

[39]:
ritas_list = loans['Rita']
[40]:
ritas_list
[40]:
['The Shining', 'Dracula', '1984']
[41]:
ritas_list[1]
[41]:
'Dracula'

Let’s see everything in Python Tutor:

[42]:
loans = {'Marco':  ['Les Misérables', 'Ulysses'],
         'Gloria': ['War and Peace'],
         'Rita':   ['The Shining','Dracula','1984']}
ritas_list = loans['Rita']
print(ritas_list[1])

jupman.pytut()
Dracula
[42]:
Python Tutor visualization

If you execute the code in Python Tutor, you will notice that as soon as we assign ritas_list, the corresponding list appears to ‘detach’ from the dictionary. This is only a graphical effect caused by Python Tutor, but from the point of view of the dictionary nothing changed. The intention is to show the list now is reachable both from the dictionary and from the new variable ritas_list.

Exercise - loans

Write some code to extract and print:

  1. The first book borrowed by Gloria ('War and Peace') and the last one borrowed by Rita ('1984')

  2. The number of books borrowed by Rita

  3. True if everybody among Marco, Gloria and Rita borrowed at least a book, False otherwise

Show solution
[43]:
loans = {'Marco':  ['Les Misérables', 'Ulysses'],
         'Gloria': ['War and Peace'],
         'Rita':   ['The Shining','Dracula','1984']}

# write here


1. The first book borrowed by Gloria is War and Peace
   The last book borrowed by Rita is 1984
2. Rita borrowed 3 book(s)
3. Have everybody borrowed at least a book? True

Exercise - Shark Bay

The West India Company asked you to explore the tropical seas, which are known fo rthe dangerous species which live in their waters. You are provided with a dmap which associates places to species found therein:

dmap = {
     "Shark Bay" : ["sharks"],
     "Estuary of Bad Luck" : ["crocodiles", "piraña"],
     "Shipwreck Trench" : ["killer whales", "tiger fishes"],
}

You are also given vague directions about how to update the dmap, using these variables:

place = "Shipwreck Trench"
dangers = ["morays", "blue spotted octupus"]
travel = "Sunken Sails Offshore"
exploration = ["barracudas", "jellyfishes"]

Try writing some code which using the variables above (or data from the map itself) MODIFIES dmap so to obtain:

>>> dmap
{'Shark Bay'           : ['sharks'],
 'Estuary of Bad Luck' : ['crocodiles', 'piraña', 'jellyfishes'],
 'Shipwreck Trench'    : ['killer whales', 'tiger fishes'],
 'Jellyfishes Offshore': ['barracudas', 'jellyfishes', 'crocodiles', 'piraña']}
  • IMPORTANT: DO NOT use constant strings in your code (so no "Shipwreck Trench" …). Numerical constants are instead allowed.

Show solution
[44]:

place = "Estuary of Bad Luck" dangers = ["morays", "blue spotted octupus"] travel = "Sunken Sails Offshore" exploration = ["barracudas", "jellyfishes"] dmap = { "Shark Bay" : ["sharks"], "Estuary of Bad Luck": ["crocodiles", "piraña"], "Shipwreck Trench" : ["killer whales", "tiger fishes"], } # write here

Exercise - The Storm Sea

The West India Company asks you now to produce a new map starting from dmap1 and dmap2. The new map must contain all the items from dmap1, expanded with the items from place1 and place2.

  • assume the items place1 and place2 are always present in dmap1 and dmap2.

  • IMPORTANT: the execution of your code must not change dmap1 nor dmap2

Example - given:

dmap1 = {
     "Shark Bay"           : ["sharks"],
     "Estuary of Bad Luck" : ["crocodiles", "piraña"],
     "Storm Sea"           : ["barracudas", "morays"]
}

dmap2 = {
     "Estuary of Bad Luck"  : ["morays", "shark fishes"],
     "Storm Sea"            : ["giant octupses"],
     "Shipwreck Trench"     : ["killer whales"],
     "Lake of the Hopeless" : ["water vortexes"]
}

place1, place2 = "Estuary of Bad Luck", "Storm Sea"

After your code, it must result:

>>> new
{'Estuary of Bad Luck': ['crocodiles', 'piraña', 'morays', 'shark fishes'],
 'Shark Bay': ['sharks'],
 'Storm Sea': ['barracudas', 'morays', 'giant octupses']}
>>> dmap1  # not changed
{'Estuary of Bad Luck': ['crocodiles', 'piraña'],
 'Shark Bay': ['sharks'],
 'Storm Sea': ['barracudas', 'morays']}
>>> dmap2  # not changed
{'Estuary of Bad Luck': ['morays', 'shark fishes'],
 'Lake of the Hopeless': ['water vortexes'],
 'Shipwreck Trench': ['killer whales'],
 'Storm Sea': ['giant octupses']}
Show solution
[45]:

dmap1 = { "Shark Bay" : ["sharks"], "Estuary of Bad Luck" : ["crocodiles", "piraña"], "Storm Sea" : ["barracudas", "morays"] } dmap2 = { "Estuary of Bad Luck" : ["morays", "shark fishes"], "Storm Sea" : ["giant octupses"], "Shipwreck Trench" : ["killer whales"], "Lake of the Hopeless" : ["water vortexes"] } place1, place2 = "Estuary of Bad Luck", "Storm Sea" # write here

Equality

We can verify whether two dictionaries are equal with == operator, which given two dictionaries return True if they contain kequal ey/value couples or False otherwise:

[46]:
{'a':3, 'b':4} == {'a':3, 'b':4}
[46]:
True
[47]:
{'a':3, 'b':4} == {'c':3, 'b':4}
[47]:
False
[48]:
{'a':3, 'b':4} == {'a':3, 'b':999}
[48]:
False

We can verify equality of dictionaries with a different number of elements:

[49]:
{'a':3, 'b':4} == {'a':3}
[49]:
False
[50]:
{'a':3, 'b':4} == {'a':3,'b':3,'c':5}
[50]:
False

… and with heterogenous elements:

[51]:
{'a':3, 'b':4} == {2:('q','p'), 'b':[99,77]}
[51]:
False

Equality and order

From the definition:

  • Keys are immutable, don’t have order and there cannot be duplicates

Since order has no importance, dictionaries created by inserting the same key/value couples in a differenct order will be considered equal.

For example, let’s try direct creation:

[52]:
{'a':5, 'b':7} == {'b':7, 'a':5}
[52]:
True

What about incremental update?

[53]:
diz1 = {}
diz1['a'] = 5
diz1['b'] = 7

diz2 = {}
diz2['b'] = 7
diz2['a'] = 5

print(diz1 == diz2)
True

QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):

  1. {1:2} == {2:1}
    
  2. {1:2,3:4} == {3:4,1:2}
    
  3. {'a'.upper():3} == {'a':3}
    
  4. {'A'.lower():3} == {'a':3}
    
  5. {'a': {1:2} == {3:4}}
    
  6. diz1 = {}
    diz1[2] = 5
    diz1[3] = 7
    
    diz2 = {}
    diz2[3] = 7
    diz2[2] = 5
    print(diz1 == diz2)
    
  7. diz1 = {'a':3,'b':8}
    diz2 = diz1
    diz1['a'] = 7
    print(diz1 == diz2)
    
  8. diz1 = {}
    diz1['a']=3
    diz2 = diz1
    diz2['a']=4
    print(diz1 == diz2)
    
  9. diz1 = {'a':3, 'b':4, 'c':5}
    diz2 = {'a':3,'c':5}
    del diz1['a']
    print(diz1 == diz2)
    
  10. diz1 = {}
    diz2 = {'a':3}
    diz1['a'] = 3
    diz1['b'] = 5
    diz2['b'] = 5
    print(diz1 == diz2)
    

Equality and copies

When duplicating containers which hold mutable objects, if we do not pay attention we might get surprises. Let’s go back on the topic of shallow and deep copies of dictionaries, this time trying to verify the effective equality in Python.

WARNING: Have you read Dictionaries 1 - Copying a dictionary ?

If not, do it now!

QUESTION: Let’s see a simple example, with a ‘manual’ copy. If you execute the following code in Python Tutor, what will it print? How many memory regions will you see?

d1 = {'a':3,
      'b':8}
d2 = {'a':d1['a'],
      'b':d1['b'] }
d1['a'] = 6

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)

NOTE: all values (3 and 8) are immutable.

Show answer
[54]:
d1 = {'a':3,
      'b':8}
d2 = {'a':d1['a'],
      'b':d1['b'] }
d1['a'] = 6

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)

jupman.pytut()
equal? False
d1= {'a': 6, 'b': 8}
d2= {'a': 3, 'b': 8}
[54]:
Python Tutor visualization

QUESTION: If you execute the following code in Python Tutor, what will it print?

  1. Which type of copy did we do? Shallow? Deep? (or both …?)

  2. How many memory regions will you see?

d1 = {'a':3,
      'b':8}
d2 = dict(d1)
d1['a'] = 7

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)
Show answer
[55]:
d1 = {'a':3,
      'b':8}
d2 = dict(d1)
d1['a'] = 7

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)

jupman.pytut()
equal? False
d1= {'a': 7, 'b': 8}
d2= {'a': 3, 'b': 8}
[55]:
Python Tutor visualization

QUESTION: If you execute the following code in Python Tutor, what will it print?

  1. Which type of copy did we do? Shallow? Deep? (or both …?)

  2. How many memory regions will you see?

NOTE: the values are lists, thus they are mutable

d1 = {'a':[1,2],
      'b':[4,5,6]}
d2 = dict(d1)
d1['a'].append(3)

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)
Show answer
[56]:
d1 = {'a':[1,2],
      'b':[4,5,6]}
d2 = dict(d1)
d1['a'].append(3)

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)

jupman.pytut()
equal? True
d1= {'a': [1, 2, 3], 'b': [4, 5, 6]}
d2= {'a': [1, 2, 3], 'b': [4, 5, 6]}
[56]:
Python Tutor visualization

QUESTION: If you execute the following code in Python Tutor, what will it print?

  1. Which type of copy did we do? Shallow? Deep? (or both …?)

  2. How many memory regions will you see?

NOTE: the values are lists, so they are mutable

import copy
d1 = {'a':[1,2],
      'b':[4,5,6]}
d2 = copy.deepcopy(d1)
d1['a'].append(3)

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)
Show answer
[57]:
import copy
d1 = {'a':[1,2],
      'b':[4,5,6]}
d2 = copy.deepcopy(d1)
d1['a'].append(3)

print('equal?', d1 == d2)
print('d1=', d1)
print('d2=', d2)

jupman.pytut()
equal? False
d1= {'a': [1, 2, 3], 'b': [4, 5, 6]}
d2= {'a': [1, 2], 'b': [4, 5, 6]}
[57]:
Python Tutor visualization

QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):

  1. diz1 = {'a':[4,5],
            'b':[6,7]}
    diz2 = dict(diz1)
    diz2['a'] = diz1['b']
    diz2['b'][0] = 9
    print(diz1 == diz2)
    print(diz1)
    print(diz2)
    
  2. da = {'a':['x','y','z']}
    db = dict(da)
    db['a'] = ['w','t']
    dc = dict(db)
    print(da)
    print(db)
    print(dc)
    
  3. import copy
    
    la = ['x','y','z']
    diz1 = {'a':la,
            'b':la }
    diz2 = copy.deepcopy(diz1)
    diz2['a'][0] = 'w'
    print('uguali?', diz1 == diz2)
    print('diz1=', diz1)
    print('diz2=', diz2)
    

Exercise - Zoom Doom

Write some code which given a string s (i.e. 'ZOOM'), creates a dictionary zd and assigns to keys 'a', 'b' and 'c' the same identical list containing the string characters as elements (i.e. ['Z','O','O','M']).

  • in Python Tutor you should see 3 arrows which go from keys to the same identical memory region

  • by modifying the list associated to each key, you should see the modification also in the lists associated to other keys

  • your code must work for any string s

Example - given:

s = 'ZOOM'

After your code, it should result:

>>> print(zd)
{'a': ['Z', 'O', 'O', 'M']
 'b': ['Z', 'O', 'O', 'M'],
 'c': ['Z', 'O', 'O', 'M'],
}
>>> zd['a'][0] = 'D'
>>> print(zd)
{'a': ['D', 'O', 'O', 'M']
 'b': ['D', 'O', 'O', 'M'],
 'c': ['D', 'O', 'O', 'M'],
}
Show solution
[58]:

s = 'ZOOM' # write here

Continue

Go on reading Dictionaries 3 - methods

[ ]: