Dictionaries 2 - operators#
Download exercise zip#
There are several operators to manipulate dictionaries:
Operator |
Syntax |
Return |
Description |
|---|---|---|---|
|
|
Retorn the number of keys |
|
dict |
obj |
Return the value associated to the key |
|
dict |
Adds or modify the value associated to the key |
||
|
Removes the key/value couple |
||
obj |
|
Return |
|
|
|
Checks whether two dictionaries are equal or different |
What to do#
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 !
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.ipynbGo 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 + Enterto execute Python code inside a Jupyter cell AND select next cell, press
Shift + Enterto execute Python code inside a Jupyter cell AND a create a new cell aftwerwards, press
Alt + EnterIf 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):
len(dict())
len({'a':{}})
len({(1,2):{3},(4,5):{6},(7,8):{9}})
len({1:2,1:2,2:4,2:4,3:6,3:6})
len({1:2,',':3,',':4,})
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] )
ANSWER: Absolutely NOT. The couples key/value in the dictionary are not ordered, so it makes no sense to get a value at a given position.
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'
}
python kabbalah[0]python kabbalah[1]python kabbalah[2]python kabbalah[3]python kabbalah[4]python kabbalah[5]python kabbalah[-1]
ANSWER: In the dictionary we have keys which are integer numbers: so we can use numbers among square brackets, which we will call keys, but not positions.
The unique expressions which will produce results are those for which the number specified among the square brackets is effectively present among the keys:
>>> kabbalah[1]
'Progress'
>>> kabbalah[3]
'Love'
>>> kabbalah[5]
'Creation'
All others will give KeyError, like:
>>> kabbalah[2]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-29-de66b9721e9b> in <module>
5 }
6
----> 7 kabbalah[2]
KeyError: 2
QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):
{'a':4,'b':5}('a')
{1:2,2:3,3:4}[2]
{'a':1,'b':2}['c']
{'a':1,'b':2}[a]
{'a':1,'b':2}[1]
{'a':1,'b':2,'c':3}['c']
{'a':1,'b':2,'c':3}[len(['a','b','c'])]
{(3,4):(1,2)}[(1,2)]
{(1,2):(3,4)}[(1,2)]
{[1,2]:[3,4]}[[1,2]]
{'a','b','c'}['a']
{'a:b','c:d'}['c']
{'a':4,'b':5}{'a'}
d1 = {'a':'b'} d2 = {'b':'c'} print(d1[d2['c']])
d1 = {'a':'b'} d2 = {'b':'c'} print(d2[d1['a']])
{}[]{[]:3}[[]]
{1:7}['1']
{'':7}"[]"
{'':7}[""]
{"":7}['']
{'"':()}['']
{():7}[()]
{(()):7}[()]
{(()):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
d1with keys'b'and'c'
Example - given:
d1 = {'a':6, 'b':2,'c':5}
After your code, it must result:
>>> print(d2)
{'z': 7}
[ ]:
d1 = {'a':6, 'b':2,'c':5}
# write here
Show solution
[ ]:
d1 = {'a':6, 'b':2,'c':5}
d2 = {'z' : d1['b'] + d1['c']}
print(d2)
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:
adding a key/value couple
associate an existing key to a different value
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):
d = {1:'a'} d[2] = 'a' print(d)
d = {} print(len(d)) d['a'] = 'b' print(len(d))
d1 = {'a':3, 'b':4} diz2 = diz1 diz1['a'] = 5 print(diz1) print(diz2)
diz1 = {'a':3, 'b':4} diz2 = dict(diz1) diz1['a'] = 5 print(diz1) print(diz2)
la = ['a','c'] diz = {'a':3, 'b':4, 'c':5} diz['d'] = diz[la[0]] + diz[la[1]] print(diz)
diz = {} diz[()]: '' diz[('a',)]: 'A' diz[('a','b')]: 'AB' print(diz)
la = [5,8,6,9] diz = {} diz[la[0]]=la[2] diz[la[2]]=la[0] print(diz)
diz = {} diz[(4,5,6)[2]] = 'c' diz[(4,5,6)[1]] = 'b' diz[(4,5,6)[0]] = 'a' print(diz)
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:
set the
'bolts'key value equal to the value of the'pincers'keyincrement the value of
wheelskey of1
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}
[ ]:
workshop = {'wheels' : 3,
'bolts' : 2,
'pincers': 5}
# write here
Show solution
[ ]:
workshop = {'wheels' : 3,
'bolts' : 2,
'pincers': 5}
workshop['wheels'] = workshop['wheels'] + 1
workshop['bolts'] = workshop['pincers']
QUESTION: Look at the following code fragments expressions, and for each try guessing the result it produces (or if it gives an error):
diz = {'a':'b'} diz['a'] = 'a' print(diz)
diz = {'1':'2'} diz[1] = diz[1] + 5 # nasty print(diz)
diz = {1:2} diz[1] = diz[1] + 5 print(diz)
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):
diz = {'a':'b'} del diz['b'] print(diz)
diz = {'a':'b', 'c':'d'} del diz['a'] print(diz)
diz = {'a':'b', 'c':'d'} del diz['a'] del diz['a'] print(diz)
diz = {'a':'b'} new_diz = del diz['a'] print(diz) print(new_diz)
diz1 = {'a':'b', 'c':'d'} diz2 = diz1 del diz1['a'] print(diz1) print(diz2)
diz1 = {'a':'b', 'c':'d'} diz2 = dict(diz1) del diz1['a'] print(diz1) print(diz2)
diz = {'a':'b'} del diz['c'] print(diz)
diz = {'a':'b'} diz.del('a') print(diz)
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 =
[ ]:
desktop = {
'paper' :5,
'pencils':2,
'pens' :3
}
# write here
Show solution
[ ]:
desktop = {
'paper' :5,
'pencils':2,
'pens' :3
}
desktop['papers'] = 4
del desktop['pens']
desktop['pencil sharpeners'] = 1
print(desktop)
Exercise - garden#
You have a dictionary garden which associates the names of present objects and their quantity. You are given:
a list
to_removecontaining the names of exactly two objects to eliminatea dictionary
to_addcontaining 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
gardenalways contains the objects given into_removeandto_addassume that
to_addalways and only containstulipsandroses
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}
[ ]:
to_remove = ['weeds', 'litter']
to_add = { 'tulips': 4,
'roses' : 2
}
garden = { 'sunflowers': 3,
'tulips' : 7,
'weeds' : 10,
'roses' : 5,
'litter' : 6,
}
# write here
Show solution
[ ]:
to_remove = ['weeds', 'litter']
to_add = { 'tulips': 4,
'roses' : 2
}
garden = { 'sunflowers': 3,
'tulips' : 7,
'weeds' : 10,
'roses' : 5,
'litter' : 6,
}
del garden[to_remove[0]]
del garden[to_remove[1]]
garden['roses'] = garden['roses'] + to_add['roses']
garden['tulips'] = garden['tulips'] + to_add['tulips']
print(garden)
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_italways and only contains translations ofhelloandroadassume that
it_esalways and only contains translations ofciaoandstradain the solution, ONLY use the constants
'hello'and'road', you will take the others you need from the dictionariesDO 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'}
[ ]:
en_it = {
'hello' : 'ciao',
'road' : 'strada'
}
it_es = {
'ciao' : 'hola',
'strada' : 'carretera'
}
en_es = {}
# write here
Show solution
[ ]:
en_it = {
'hello' : 'ciao',
'road' : 'strada'
}
it_es = {
'ciao' : 'hola',
'strada' : 'carretera'
}
en_es = {}
en_es['hello'] = it_es[en_it['hello']]
en_es['road'] = it_es[en_it['road']]
print(en_es)
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):
('a') in {'a':5}
('a','b') in {('a','b'):5}
('a','b',) in {('a','b'):5}
['a','b'] in {('a','b'):5}
{3: 'q' in {'q':5}}
{'q' not in {'q':0} : 'q' in {'q':0}}
{'a' in 'b'}
{'a' not in {'b':'a'}}
len({'a':6,'b':4}) in {1:2}
'ab' in {('a','b'): 'ab'}
None in {}
None in {'None':3}
None in {None:3}
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.
DO NOT use
ifDO NOT use loops nor list comprehensions
HINT: if you don’t know how to do it, look at Booleans - Evaluation order
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
[ ]:
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
Show solution
[ ]:
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'}
menu[order-1] in kitchen and kitchen[menu[order-1]] == menu[order]
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 sys
sys.path.append('../')
import jupman
[36]:
loans = {'Marco': ['Les Misérables', 'Ulysses'],
'Gloria': ['War and Peace'],
'Rita': ['The Shining','Dracula','1984']}
jupman.pytut()
[36]:
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]:
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:
The first book borrowed by Gloria (
'War and Peace') and the last one borrowed by Rita ('1984')The number of books borrowed by Rita
Trueif everybody among Marco, Gloria and Rita borrowed at least a book,Falseotherwise
[ ]:
loans = {'Marco': ['Les Misérables', 'Ulysses'],
'Gloria': ['War and Peace'],
'Rita': ['The Shining','Dracula','1984']}
# write here
Show solution
[ ]:
loans = {'Marco': ['Les Misérables', 'Ulysses'],
'Gloria': ['War and Peace'],
'Rita': ['The Shining','Dracula','1984']}
print("1. The first book borrowed by Gloria is", loans['Gloria'][0])
print(" The last book borrowed by Rita is", loans['Rita'][-1])
print("2. Rita borrowed", len(loans['Rita']), "book(s)")
res = len(loans['Marco']) > 0 and len(loans['Gloria']) > 0 and len(loans['Rita']) > 0
print("3. Have everybody borrowed at least a book?", res)
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.
[ ]:
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
Show solution
[ ]:
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"],
}
dmap[travel] = dangers
dmap[exploration[1].capitalize() + travel[-9:]] = exploration + dmap[place]
dmap[place].append(exploration[1])
del dmap[travel]
dmap
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
place1andplace2are always present indmap1anddmap2.IMPORTANT: the execution of your code must not change
dmap1nordmap2
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']}
[ ]:
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
Show solution
[ ]:
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"
import copy
new = copy.deepcopy(dmap1)
new[place1].extend(dmap2[place1])
new[place2].extend(dmap2[place2])
from pprint import pprint
print("new:")
pprint(new)
print("dmap1:")
pprint(dmap1)
print("dmap2:")
pprint(dmap2)
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:2} == {2:1}
{1:2,3:4} == {3:4,1:2}
{'a'.upper():3} == {'a':3}
{'A'.lower():3} == {'a':3}
{'a': {1:2} == {3:4}}
diz1 = {} diz1[2] = 5 diz1[3] = 7 diz2 = {} diz2[3] = 7 diz2[2] = 5 print(diz1 == diz2)
diz1 = {'a':3,'b':8} diz2 = diz1 diz1['a'] = 7 print(diz1 == diz2)
diz1 = {} diz1['a']=3 diz2 = diz1 diz2['a']=4 print(diz1 == diz2)
diz1 = {'a':3, 'b':4, 'c':5} diz2 = {'a':3,'c':5} del diz1['a'] print(diz1 == diz2)
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.
ANSWER: In this case we manually created a dictionary d2 using immutable values taken from d1. So in Python Tutor we will see two distinct memory regions and a successive modification to d1 will not alter d2:
[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]:
QUESTION: If you execute the following code in Python Tutor, what will it print?
Which type of copy did we do? Shallow? Deep? (or both …?)
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)
ANSWER: when used as a function, dict executes a shallow copy, that is, copies the structure of the dictionary without duplicating the mutable values. In this specific case, all values we have are immutable integers, so the copy can also be considered a complete duplication. When we assign the value 7 to the key 'a' in d1 we are modifying the original data structure , leaving the copy we just made d2 unaltered, so d1 == d2 will be False.
Let’s verify it in Python Tutor:
[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]:
QUESTION: If you execute the following code in Python Tutor, what will it print?
Which type of copy did we do? Shallow? Deep? (or both …?)
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)
ANSWER: We used dict like a function, so we did a shallow copy. In this case we have lists as values, which are mutable objects. This means the shallow copy only copied references to the lists, but not the lists themselves. For this reason you will see arrows going from the copy of the dictionary d2 to memory regions of the original lists. This means that if you try to modify a list after the copy occurred (for example with the method .append(3)), as a matter of fact you
will also modify the list reachable from the copied dictionary d2. Let’s check this out in Python Tutor:
[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]:
QUESTION: If you execute the following code in Python Tutor, what will it print?
Which type of copy did we do? Shallow? Deep? (or both …?)
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)
ANSWER: We used copy.deepcopy, making an in-depth copy. In this case we have mutable lists as values. The deep copy duplicated all the objects it was able to reach, lists included. So in this case we will obtain two completely distinct memory regions. After the copy, if we modify a list reachable from the original d1, we will be sure that we cannot tarnish objects reachable from d2. Let’s check it in Python Tutor:
[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]:
QUESTION: Look at the following code fragments, and for each try guessing which result it produces (or if it gives an error):
python 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)python da = {'a':['x','y','z']} db = dict(da) db['a'] = ['w','t'] dc = dict(db) print(da) print(db) print(dc)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'],
}
[ ]:
s = 'ZOOM'
# write here
Show solution
[ ]:
s = 'ZOOM'
zoom = list(s)
zd = {'a':zoom,
'b':zoom,
'c':zoom}
print(zd)
zd['a'][0] = 'D'
print(zd)
Continue#
Go on with Dictionaries 3 - Methods