Dictionaries 1 - Introduction
Download exercises zip
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
Given a key, we can find the corresponding value very fast.
What to do
Unzip exercises zip in a folder, you should obtain something like this:
sets
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
dictionaries1.ipynb
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
Creating a dictionary
In everyday life, when thinking about a dictionary we typically refer to a book which given an item (for example 'chair'
), allows us to rapidly find the related description (i.e. a piece of furniture to sit on
).
In Python we have a data structure called dict
which provides an easy way to represent dictionaries.
Following the previous example, we might create a dict
with different items like this:
[2]:
{'chair':'a piece of furniture to sit on',
'cupboard':'a cabinet for storage',
'lamp': 'a device to provide illumination'
}
[2]:
{'chair': 'a piece of furniture to sit on',
'cupboard': 'a cabinet for storage',
'lamp': 'a device to provide illumination'}
Let’s be clear about the naming:
Dictionaries are mutable containers which allow us to rapidly associate elements called keys to some values.
The definition says we have keys (in the example 'chair'
, 'cupboard'
, etc), while the descriptions from the example ('a piece of furniture to sit on'
) in Python are going to be called values.
When we create a dictionary, we first write a curly bracket {
, then we follow it with a series of key :
value couples, each followed by a comma ,
(except the last one, in which the comma is optional). At the end we close with a a curly bracket }
Placing spaces or newlines inside is optional. So we can also write like this:
[3]:
{'chair' : 'a piece of furniture to sit on',
'cupboard' : 'a cabinet for storage',
'lamp' : 'a device to provide illumination'
}
[3]:
{'chair': 'a piece of furniture to sit on',
'cupboard': 'a cabinet for storage',
'lamp': 'a device to provide illumination'}
Or also everything on a row:
[4]:
{'chair':'a piece of furniture to sit on','cupboard':'a cabinet for storage','lamp':'a device to provide illumination'}
[4]:
{'chair': 'a piece of furniture to sit on',
'cupboard': 'a cabinet for storage',
'lamp': 'a device to provide illumination'}
Note if we use short words Python will probably print the dictionary in single a row anyway:
[5]:
{'barca': 'remo',
'auto': 'ruota',
'aereo': 'ala'}
[5]:
{'barca': 'remo', 'auto': 'ruota', 'aereo': 'ala'}
Putting a comma after the last couple does not give errors:
[6]:
{
'ship': 'paddle',
'car': 'wheel',
'airplane': 'wing', # note 'extra' comma
}
[6]:
{'ship': 'paddle', 'car': 'wheel', 'airplane': 'wing'}
Let’s see how a dictionary is represented in Python Tutor - to ease the job, we will assign the variable furniture
to it
[7]:
# WARNING: FOR PYTHON TUTOR TO WORK, REMEMBER TO EXECUTE THIS CELL with Shift+Enter
# (it's sufficient to execute it only once)
import jupman
[8]:
furniture = {
'chair' :'a piece of furniture to sit on',
'cupboard':'a cabinet for storage',
'lamp' :'a device to provide illumination'
}
print(furniture)
jupman.pytut()
{'chair': 'a piece of furniture to sit on', 'cupboard': 'a cabinet for storage', 'lamp': 'a device to provide illumination'}
[8]:
We note that once executed, an arrow appears pointing from furniture
to an orange/yellow memory region. The keys have orange background, while the corresponding values have yellow background. Looking at arrows and colors, we can guess that whenever we’re assigning variables, dictionaries behave like other data structures, like lists and sets.
QUESTION: Look at the following code, and try guessing what happens during execution - at the end, how will memory be organized? What will be printed? Where will arrows go?
[9]:
da = {
'chair' : 'a piece of furniture to sit on',
'cupboard' : 'a cabinet for storage',
'lamp' : 'a device to provide illumination'
}
db = {
'ship': 'paddle',
'car': 'wheel',
'airplane': 'wing'
}
dc = db
db = da
da = dc
dc = db
#print(da)
#print(db)
#print(dc)
jupman.pytut()
[9]:
The keys
Let’s try to better understand which keys we can use by looking again at the definition:
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
QUESTION: have a careful look at the words in bold - can you tell a data structure we’ve already seen which has these features?
Show answerKeys are immutable
QUESTION: The definition does not force us to use strings as keys, other types are also allowed. But can we use all the types we want?
For each of the following examples, try to tell whether the dictionary can be created or we will get an error (which one?). Also check how they are represented in Python Tutor.
integers
{ 4 : 'cats', 3 : 'dogs' }
float
{ 4.0 : 'cats', 3.0 : 'dogs' }
strings
{ 'a' : 'cats', 'b' : 'dogs' }
lists
{ [1,2] : 'zam', [3,4] : 'zum' }
tuples
{ (1,2) : 'zam', (4,3) : 'zum' }
sets
{ {1,2} : 'zam', {3,4} : 'zum' }
other dictionaries (check the first part of the definition !)
{ {'a':'x','b':'y'} : 'zam', {'c':'w','d':'z'} : 'zum' }
Keys don’t have order
In a real-life dictionary, items are always ordered according to some criteria, typically in alphabetical order.
With Python we need to consider this important difference:
The keys are immutable, don’t have order and there cannot be duplicates
When we say that a collection ‘does not have order’, it means that the order of elements we see when we insert or print them does not matter to determine whether a collection is equal to another one. In dictionaries, it means that if we specify couples in a different order, we obtain dictionaries that Python considers as equal.
For example, the following dictionaries can all be considered as equal:
[10]:
{
'ships' :'port',
'airplanes': 'airport',
'trains': 'station'
}
[10]:
{'ships': 'port', 'airplanes': 'airport', 'trains': 'station'}
[11]:
{
'airplanes': 'airport',
'ships' :'port',
'trains': 'station'
}
[11]:
{'airplanes': 'airport', 'ships': 'port', 'trains': 'station'}
[12]:
{
'trains': 'station',
'ships' :'port',
'airplanes': 'airport'
}
[12]:
{'trains': 'station', 'ships': 'port', 'airplanes': 'airport'}
Printing a dictionary: you may have noticed that Jupyter always prints the keys in alphabetical order. This is just a courtesy for us, but do not be fooled by it! If we try a native print
we will obtain a different result!
[13]:
print({
'ships' :'port',
'airplanes': 'airport',
'trains': 'station'
})
{'ships': 'port', 'airplanes': 'airport', 'trains': 'station'}
Key duplicates
Keys are immutable, don’t have order and there cannot be duplicates
We might ask ourselves how Python manages duplicates in keys. Let’s try to create a duplicated couple on purpose:
[14]:
{
'chair' : 'a piece of furniture to sit on',
'chair' : 'a piece of furniture to sit on',
'lamp' : 'a device to provide illumination'
}
[14]:
{'chair': 'a piece of furniture to sit on',
'lamp': 'a device to provide illumination'}
We notice Python didn’t complain and silently discarded the duplicate.
What if we try inserting a couple with the same key but different value?
[15]:
{
'chair' : 'a piece of furniture to sit on',
'chair' : 'a type of seat',
'lamp' : 'a device to provide illumination'
}
[15]:
{'chair': 'a type of seat', 'lamp': 'a device to provide illumination'}
Notice Python kept only the last couple.
The values
Let’s see once again the definition:
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
Seems like values have less constraints than keys.
QUESTION: For each of the following examples, try to tell whether we can create the dictionary or we will get an error (which one?). Check how they are represented in Python Tutor.
integers
{ 'a':3, 'b':4 }
duplicated integers
{ 'a':3, 'b':3 }
float
{ 'a':3.0, 'b':4.0 }
strings
{ 'a' : 'ice', 'b' : 'fire' }
lists
{ 'a' : ['t','w'], 'b' : ['x'], 'c' : ['y','z','k'] }
duplicated lists
{ 'a' : ['x','y','z'], 'b' : ['x','y','z'] }
lists containing duplicates
{ 'a' : ['x','y','y'], 'b' : ['z','y','z'] }
tuples
{ 'a': (6,9,7), 'b': (8,1,7,4) }
sets
{ 'a' : {6,5,6}, 'b' : {2,4,1,5} }
dictionaries
{ 'a': { 'x':3, 'y':9 }, 'b': { 'x':3, 'y':9, 'z':10 }, }
Empty dictionary
We can create an empty dictionary by writing {}
:
WARNING: THIS IS NOT THE EMPTY SET !!
[16]:
{}
[16]:
{}
[17]:
type({})
[17]:
dict
A dictionary is a collection, and as we’ve already seen (with lists, tuples and sets), we can create an empty collection by typing its type, in this case dict
, followed by round brackets:
[18]:
dict()
[18]:
{}
Let’s see how it’s represented in Python Tutor:
[19]:
diz = dict()
jupman.pytut()
[19]:
Keys and heterogenous values
So far we’ve always used keys all of the same type and values all of the same type, but this is not mandatory. (the only required thing is for key types to be immutable):
[20]:
{
"a": 3,
"b": ["a", "list"],
7 : ("this","is","a","tuple")
}
[20]:
{'a': 3, 'b': ['a', 'list'], 7: ('this', 'is', 'a', 'tuple')}
NOTE: Although mixing types is possible, it’s not advisable!
Throwing different types inside a dictionary often brings misfortune, as it increases probability of incurring into bugs.
QUESTION: Look at the following expressions, and for each try guessing the result (or if it gives an error):
{'a':'b' , 'c':'d' }
{'a b':'c', 'c d':'e f'}
{'a' = 'c', 'b' = 'd'}
{'a':'b': 'c':'d'}
{ "1":[2,3], "2,3":1, }
type({'a:b,c:d'})
{'a':'b'; 'c':'d'}
{'a:b', 'c:d'}
{5,2: 4,5}
{1:2, 1:3}
{2:1, 3:1}
{'a':'b', 'c':'d',}
type({'a','b', 'c','d'})
{'a':'b', 'c':'d', 'e','f'}
{{}: 2}
{(1,2):[3,4]}
{[1,2]:(3,4)}
{'[1,2]':(3,4)}
{{1,2}:(3,4)}
{len({1,2}):(3,4)}
{5:{'a':'b'}}
{"a":{1:2}}
{"a":{[1]:2}}
{"a":{1:[2]}}
{["a":{1:[2]}]}
set([{2:4}])
Exercise - barone
Given a lst
of exactly 6 characters, build a dictionary diz
as follows:
Example 1 - given:
lst = ['b', 'a', 'r', 'o', 'n', 'e']
after your code it must result (NOTE: the key order DOESN’T matter!)
>>> diz
{'b': ['a', 'r', 'o', 'n', 'e'],
('b', 'a', 'r', 'o', 'n', 'e'): {'a', 'b', 'e', 'n', 'o', 'r'},
('b', 'a', 'b', 'a'): ['r', 'o', 'r', 'o', 'n', 'e', 'n', 'e'],
'b/a/r/o/n/e': {'b': 'a', 'r': 'o', 'n': 'e'}}
Example 2 - given:
lst = ['p', 'r', 'i', 'o', 'r', 'e']
it must result:
>>> diz
{'p': ['r', 'i', 'o', 'r', 'e'],
('p', 'r', 'i', 'o', 'r', 'e'): {'e', 'i', 'o', 'p', 'r'},
('p', 'r', 'p', 'r'): ['i', 'o', 'i', 'o', 'r', 'e', 'r', 'e'],
'p/r/i/o/r/e': {'p': 'r', 'i': 'o', 'r': 'e'}}
USE only
lst
IMPORTANT: DO NOT write string constants (so no
"barone"
,"b"
….)
[21]:
lst = ['b', 'a', 'r', 'o', 'n', 'e']
lst = ['p', 'r', 'i', 'o', 'r', 'e']
# write here
Dictionary from a sequence of couples
We can obtain a dictionary by specifying a sequence of key/value couples as parameter of the function dict
. For example we could pass a list of tuples:
[22]:
dict( [
('flour',500),
('eggs',2),
('sugar',200),
])
[22]:
{'flour': 500, 'eggs': 2, 'sugar': 200}
We can also use other sequences, the important bit is that subsequences must all have two elements. For example, here is a tuple of lists:
[23]:
dict( (
['flour',500],
['eggs',2],
['sugar',200],
))
[23]:
{'flour': 500, 'eggs': 2, 'sugar': 200}
If a subsequence has a number of elements different from two, we obtain this error:
>>> dict( (
['flour',500],
['rotten','eggs', 3],
['sugar',200],
))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-88-563d301b4aef> in <module>
2 ['flour',500],
3 ['rotten','eggs', 3],
4 ['sugar',200],
5 ))
ValueError: dictionary update sequence element #1 has length 3; 2 is required
QUESTION: Compare the following expressions. Do they do the same thing? If so, which one would you prefer?
dict( {
('a',5),
('b',8),
('c',3)
} )
dict( (
{'a',5},
{'b',8},
{'c',3}
)
)
QUESTION: Look at the following expressions, and for each try guessing which result it produces (or if it gives an error):
dict('abcd')
dict(['ab','cd'])
dict(['a1','c2'])
dict([])
dict(())
dict((' ',)) # nasty
Exercise - galattico veramente
Given some variables use the constructor from sequences of couples to obtain the variable diz
DO NOT use string constants in the code, nor particular numbers (so no
'Ga'
nor759
). Using indexes is allowed.
Example 1 - given:
s = 'Ga'
t = ('LA','tt')
l1 = ['Ic','Co','Ve']
l2 = ['Ra','Me','Nt']
l3 = [[['EEE','...']]]
n = 43.759
After your code, it must result (NOTE: the order of keys DOESN’T matter!)
>>> diz
{'G': 'a',
'LA': 'tt',
'I': 'c',
'C': 'o',
'V': 'e',
'R': 'a',
'M': 'e',
'N': 't',
'EEE': '...',
'43': '759'}
Example 2 - given:
s = 'Sp'
t = ('Az','ia')
l1 = ['Le','Si','De']
l2 = ['Ra','Le','In']
l3 = [[['CREDIBBILE','!!!!!']]]
n = 8744.92835
must result in:
>>> diz
{'S': 'i',
'Az': 'ia',
'L': 'e',
'D': 'e',
'R': 'a',
'I': 'n',
'CREDIBBILE': '!!!!!',
'8744': '92835'}
[24]:
s = 'Ga'
t = ('LA','tt')
l1 = ['Ic','Co','Ve']
l2 = ['Ra','Me','Nt']
l3 = [[['EEE','...']]]
n = 43.759
#s = 'Sp'
#t = ('Az','ia')
#l1 = ['Le','Si','De']
#l2 = ['Ra','Le','In']
#l3 = [[['CREDIBBILE','!!!!!']]]
#n = 8744.92835
# write here
Dictionary from keyword arguments
As further creation method, we can specify keys as they were parameters with a name:
[25]:
dict(a=5,b=6)
[25]:
{'a': 5, 'b': 6}
WARNING: keys will be subject to the same restrictive rules of function parameter names!
For example, by using curly brackets this dictionary is perfectly lecit:
[26]:
{'a b' : 2,
'c d' : 6}
[26]:
{'a b': 2, 'c d': 6}
But if we try creating it using a b
as argument of dict
, we will incur into problems:
>>> dict(a b=2, c d=6)
File "<ipython-input-97-444f8661585a>", line 1
dict(a b=2, c d=6)
^
SyntaxError: invalid syntax
Strings will also give trouble:
>>> dict('a b'=2,'c d'=6)
File "<ipython-input-98-45aafbb56e81>", line 1
dict('a b'=2,'c d'=6)
^
SyntaxError: keyword can't be an expression
And be careful about tricks like using variables, we won’t obtain the desired result:
[27]:
ka = 'a b'
kc = 'c d'
dict(ka=2,kc=6)
[27]:
{'ka': 2, 'kc': 6}
QUESTION: Look at the following expressions, and for each try guessing the result (or if it gives an error):
dict(3=5,2=8)
dict('costs'=9,'benefits'=15)
dict(_costs=9,_benefits=15)
dict(33trentini=5)
dict(trentini33=5)
dict(trentini_33=5)
dict(trentini-33=5)
dict(costs=1=2,benefits=3=3)
dict(costs=1==2,benefits=3==3)
v1 = 6 v2 = 8 dict(k1=v1,k2=v2)
Copying a dictionary
There are two ways to copy a dictionary, you can either do a shallow copy or a deep copy.
Shallow copy
It is possible to create a shallow copy by passing another dictionary to function dict
:
[28]:
da = {'x':3,
'y':5,
'z':1}
[29]:
db = dict(da)
[30]:
print(da)
{'x': 3, 'y': 5, 'z': 1}
[31]:
print(db)
{'x': 3, 'y': 5, 'z': 1}
In Python Tutor we will see two different memory regions:
[32]:
da = {'x':3,
'y':5,
'z':1}
db = dict(da)
jupman.pytut()
[32]:
QUESTION: can we also write like this? With respect to the previous example, will we obtain different results?
[33]:
da = {'x':3,
'y':5,
'z':1}
db = dict(dict(da))
jupman.pytut()
[33]:
Mutable values: In the example we used integer values, which are immutable. If we tried mutable values like lists, what would happen?
[34]:
da = {'x':['a','b','c'],
'y':['d'],
'z':['e','f']}
db = dict(da)
jupman.pytut()
[34]:
If you try executing Python Tutor, you will see an explosion of arrows which go from the new dictionary db
to the values of da
(which are lists). No panic! We are going to give a better explanation in the next notebook, for now just note that with the shallow copy of mutable values the new dictionary will have memory regions in common with the original dictionary.
Deep copy
When there are mutable shared memory regions like in the case above, it’s easy to do mistakes and introduce subtle bugs you might notice much later in the development cycle.
In order to have completely separated memory regions, we can use deep copy.
First we must tell Python we intend to use functions from the module copy
, and then we will be allowed to call its deepcopy
function:
[35]:
from copy import deepcopy
da = {'x':['a','b','c'],
'y':['d'],
'z':['e','f']}
db = deepcopy(da)
jupman.pytut()
[35]:
If you execute the code in Python Tutor, you will notice that by following the arrow from db
we will end up in an totally new orange/yellow memory region, which shares nothing with the memory region pointed by da
.
QUESTION: Have a look at the following code - after its execution, will you see arrows going from db
to elements of da
?
[36]:
da = {'x': {1,2,3},
'y': {4,5}}
db = dict(da)
jupman.pytut()
[36]:
Continue
Go on reading Dictionaries 2
[ ]: