Dictionaries 1 - Introduction

Download exercises zip

Browse files online

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

  1. 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.ipynb
    dictionaries5-sol.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 dictionaries1.ipynb

  2. Go on reading the exercises file, sometimes you will find paragraphs marked Exercises graded from ✪ to ✪✪✪✪ 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]:
{'aereo': 'ala', 'auto': 'ruota', 'barca': 'remo'}

Putting a comma after the last couple does not give errors:

[6]:
{
 'ship': 'paddle',
 'car': 'wheel',
 'airplane': 'wing',  # note 'extra' comma
}
[6]:
{'airplane': 'wing', 'car': 'wheel', 'ship': 'paddle'}

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()
{'cupboard': 'a cabinet for storage', 'chair': 'a piece of furniture to sit on', '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 answer

Keys 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.

  1. integers

    {
      4 : 'cats',
      3 : 'dogs'
    }
    
  2. float

    {
      4.0 : 'cats',
      3.0 : 'dogs'
    }
    
  3. strings

    {
      'a' : 'cats',
      'b' : 'dogs'
    }
    
  4. lists

    {
        [1,2] : 'zam',
        [3,4] : 'zum'
    }
    
  5. tuples

    {
        (1,2) : 'zam',
        (4,3) : 'zum'
    }
    
  6. sets

    {
        {1,2} : 'zam',
        {3,4} : 'zum'
    }
    
  7. other dictionaries (check the first part of the definition !)

    {
        {'a':'x','b':'y'} : 'zam',
        {'c':'w','d':'z'} : 'zum'
    }
    
Show answer

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]:
{'airplanes': 'airport', 'ships': 'port', 'trains': 'station'}
[11]:
{
 'airplanes': 'airport',
 'ships' :'port',
 'trains': 'station'
}
[11]:
{'airplanes': 'airport', 'ships': 'port', 'trains': 'station'}
[12]:
{
 'trains': 'station',
 'ships' :'port',
 'airplanes': 'airport'
}
[12]:
{'airplanes': 'airport', 'ships': 'port', 'trains': 'station'}

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'
})
{'trains': 'station', 'airplanes': 'airport', 'ships': 'port'}

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.

  1. integers

    {
        'a':3,
        'b':4
    }
    
  2. duplicated integers

    {
        'a':3,
        'b':3
    }
    
  3. float

    {
        'a':3.0,
        'b':4.0
    }
    
  4. strings

    {
        'a' : 'ice',
        'b' : 'fire'
    }
    
  5. lists

    {
        'a' : ['t','w'],
        'b' : ['x'],
        'c' : ['y','z','k']
    }
    
  6. duplicated lists

    {
        'a' : ['x','y','z'],
        'b' : ['x','y','z']
    }
    
  7. lists containing duplicates

    {
        'a' : ['x','y','y'],
        'b' : ['z','y','z']
    }
    
  8. tuples

    {
        'a': (6,9,7),
        'b': (8,1,7,4)
    }
    
  9. sets

    {
        'a' : {6,5,6},
        'b' : {2,4,1,5}
    }
    
  10. dictionaries

    {
        'a': {
                'x':3,
                'y':9
             },
        'b': {
                'x':3,
                'y':9,
                'z':10
             },
    }
    
Show answer

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]:
{7: ('this', 'is', 'a', 'tuple'), 'a': 3, 'b': ['a', 'list']}

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):

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

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:

[21]:
dict(  [
         ('flour',500),
         ('eggs',2),
         ('sugar',200),
       ])
[21]:
{'eggs': 2, 'flour': 500, '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:

[22]:
dict(  (
         ['flour',500],
         ['eggs',2],
         ['sugar',200],
       ))
[22]:
{'eggs': 2, 'flour': 500, '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}
      )
    )
Show answer

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

  1. dict('abcd')
    
  2. dict(['ab','cd'])
    
  3. dict(['a1','c2'])
    
  4. dict([])
    
  5. dict(())
    
  6. dict(('  ',))   # nasty
    

Dictionary from keyword arguments

As further creation method, we can specify keys as they were parameters with a name:

[23]:
dict(a=5,b=6)
[23]:
{'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:

[24]:
{'a b' : 2,
 'c d' : 6}
[24]:
{'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:

[25]:
ka = 'a b'
kc = 'c d'

dict(ka=2,kc=6)
[25]:
{'ka': 2, 'kc': 6}

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

  1. dict(3=5,2=8)
    
  2. dict('costs'=9,'benefits'=15)
    
  3. dict(_costs=9,_benefits=15)
    
  4. dict(33trentini=5)
    
  5. dict(trentini33=5)
    
  6. dict(trentini_33=5)
    
  7. dict(trentini-33=5)
    
  8. dict(costs=1=2,benefits=3=3)
    
  9. dict(costs=1==2,benefits=3==3)
    
  10. 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:

[26]:
da = {'x':3,
      'y':5,
      'z':1}
[27]:
db = dict(da)
[28]:
print(da)
{'y': 5, 'z': 1, 'x': 3}
[29]:
print(db)
{'y': 5, 'z': 1, 'x': 3}

In Python Tutor we will see two different memory regions:

[30]:
da = {'x':3,
      'y':5,
      'z':1}
db = dict(da)

jupman.pytut()
[30]:

QUESTION: can we also write like this? With respect to the previous example, will we obtain different results?

[31]:
da = {'x':3,
      'y':5,
      'z':1}
db = dict(dict(da))

jupman.pytut()
[31]:
Show answer

Mutable values: In the example we used integer values, which are immutable. If we tried mutable values like lists, what would happen?

[32]:
da = {'x':['a','b','c'],
      'y':['d'],
      'z':['e','f']}
db = dict(da)

jupman.pytut()
[32]:

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:

[33]:
from copy import deepcopy

da = {'x':['a','b','c'],
      'y':['d'],
      'z':['e','f']}
db = deepcopy(da)

jupman.pytut()
[33]:

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?

[34]:
da = {'x': {1,2,3},
      'y': {4,5}}
db = dict(da)
jupman.pytut()
[34]:
Show answer

Continue

Go on reading Dictionaries 2

[ ]: