6.1. Mapping Dict

6.1.1. Rationale

  • dict are key-value storage

  • key lookup is very efficient O(1)

  • Mutable - can add, remove, and modify items

6.1.2. Definition

>>> data = {}
>>> data = dict()
>>> data = {
...    1961: 'First Human Space Flight',
...    1969: 'First Step on the Moon'}
>>> data = {
...    1961: ['First Russian Space Flight', 'First US Space Flight'],
...    1969: ['First Step on the Moon']}
>>> data = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>> data = dict(
...     commander='Melissa Lewis',
...     botanist='Mark Watney',
...     chemist='Alex Vogel')

Duplicating items are overridden by latter:

>>> data = {
...     'commander': 'Melissa Lewis',
...     'commander': 'Jan Twardowski'}
>>>
>>> data
{'commander': 'Jan Twardowski'}

6.1.3. GetItem

  • [...] throws KeyError exception if key not found in dict

  • .get() returns None if key not found

  • .get() can have default value, if key not found

Getitem Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew['commander']
'Melissa Lewis'
>>>
>>> crew['pilot']
Traceback (most recent call last):
KeyError: 'pilot'

Get Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew.get('commander')
'Melissa Lewis'
>>>
>>> crew.get('pilot')
>>>
>>> crew.get('pilot', 'not assigned')
'not assigned'

Getting keys other than str:

>>> calendarium = {
...    1961: 'First Human Space Flight',
...    1969: 'First Step on the Moon'}
>>>
>>> calendarium[1961]
'First Human Space Flight'
>>>
>>> calendarium.get(1961)
'First Human Space Flight'
>>>
>>> calendarium['1961']
Traceback (most recent call last):
KeyError: '1961'
>>>
>>> calendarium.get('1961')
>>>
>>> calendarium.get('1961', 'unknown')
'unknown'

6.1.4. Get Keys, Values and Key-Value Pairs

  • Key can be any hashable object

In Python 2, the methods items(), keys() and values() used to "take a snapshot" of the dictionary contents and return it as a list. It meant that if the dictionary changed while you were iterating over the list, the contents in the list would not change. In Python 3, these methods return a view object whose contents change dynamically as the dictionary changes. Therefore, in order for the behavior of iterations over the result of these methods to remain consistent with previous versions, an additional call to list() has to be performed in Python 3 to "take a snapshot" of the view object contents. 1

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew.keys()
dict_keys(['commander', 'botanist', 'chemist'])
>>>
>>> crew.values()
dict_values(['Melissa Lewis', 'Mark Watney', 'Alex Vogel'])
>>>
>>> crew.items()
dict_items([('commander', 'Melissa Lewis'), ('botanist', 'Mark Watney'), ('chemist', 'Alex Vogel')])
>>>
>>> list(crew.keys())
['commander', 'botanist', 'chemist']
>>>
>>> list(crew.values())
['Melissa Lewis', 'Mark Watney', 'Alex Vogel']
>>>
>>> list(crew.items())  # doctest: +NORMALIZE_WHITESPACE
[('commander', 'Melissa Lewis'),
 ('botanist', 'Mark Watney'),
 ('chemist', 'Alex Vogel')]

6.1.5. Set Item

  • Adds if value not exist

  • Updates if value exist

Set Item Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew['pilot'] = 'Rick Martinez'
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez'}
>>>
>>> crew['commander'] = 'Jan Twardowski'
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Jan Twardowski',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez'}

Update Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew.update(pilot='Rick Martinez')
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez'}
>>>
>>> crew.update(commander='Jan Twardowski')
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Jan Twardowski',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez'}

Update Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> new = {
...    'pilot': 'Rick Martinez',
...    'surgeon': 'Chris Beck',
...    'engineer': 'Beth Johanssen'}
>>>
>>> crew.update(new)
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}

6.1.6. Delete Item

Pop Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel',
...    'pilot': 'Rick Martinez',
...    'surgeon': 'Chris Beck',
...    'engineer': 'Beth Johanssen'}
>>>
>>> left_alone_on_mars = crew.pop('botanist')
>>>
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}
>>>
>>> print(left_alone_on_mars)
Mark Watney

Popitem Method:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> last = crew.popitem()
>>>
>>> print(crew)
{'commander': 'Melissa Lewis', 'botanist': 'Mark Watney'}
>>>
>>> print(last)
('chemist', 'Alex Vogel')

Del Keyword:

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> del crew['chemist']
>>>
>>> print(crew)
{'commander': 'Melissa Lewis', 'botanist': 'Mark Watney'}

6.1.7. Merge

  • Merge (|) and update (|=) operators have been added to the built-in dict class.

  • Since Python 3.9: PEP 584 -- Add Union Operators To dict

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> new = {
...     'pilot': 'Rick Martinez',
...     'surgeon': 'Chris Beck',
...     'engineer': 'Beth Johanssen'}
>>>
>>> everyone = crew | new
>>>
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel'}
>>>
>>> print(new)  # doctest: +NORMALIZE_WHITESPACE
{'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}
>>>
>>> print(everyone)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}
>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> new = {
...    'pilot': 'Rick Martinez',
...    'surgeon': 'Chris Beck',
...    'engineer': 'Beth Johanssen'}
>>>
>>> crew |= new
>>>
>>> print(crew)  # doctest: +NORMALIZE_WHITESPACE
{'commander': 'Melissa Lewis',
 'botanist': 'Mark Watney',
 'chemist': 'Alex Vogel',
 'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}
>>>
>>> print(new)  # doctest: +NORMALIZE_WHITESPACE
{'pilot': 'Rick Martinez',
 'surgeon': 'Chris Beck',
 'engineer': 'Beth Johanssen'}

6.1.8. GetItem and Slice

  • GetItem with index on dict is not possible

  • Slicing on dict is not possible

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> crew[0]
Traceback (most recent call last):
KeyError: 0
>>> crew[1]
Traceback (most recent call last):
KeyError: 1
>>> crew[2]
Traceback (most recent call last):
KeyError: 2
>>> crew[-0]
Traceback (most recent call last):
KeyError: 0
>>> crew[-1]
Traceback (most recent call last):
KeyError: -1
>>> crew[-2]
Traceback (most recent call last):
KeyError: -2
>>> crew[1:2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'
>>> crew[:2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'
>>> crew[::2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'
>>> crew = {
...    0: 'Melissa Lewis',
...    1: 'Mark Watney',
...    2: 'Alex Vogel'}
>>>
>>> crew[0]
'Melissa Lewis'
>>> crew[1]
'Mark Watney'
>>> crew[2]
'Alex Vogel'
>>> crew[-0]
'Melissa Lewis'
>>> crew[-1]
Traceback (most recent call last):
KeyError: -1
>>> crew[-2]
Traceback (most recent call last):
KeyError: -2
>>> crew[1:2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'
>>> crew[:2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'
>>> crew[::2]
Traceback (most recent call last):
TypeError: unhashable type: 'slice'

6.1.9. Dict or Set

  • Both set and dict keys must be hashable

  • Both set and dict uses the same { and } braces

  • Despite similar syntax, they are different types

>>> data = {1, 2}
>>> type(data)
<class 'set'>
>>> data = {1: 2}
>>> type(data)
<class 'dict'>
>>> data = {1, 2, 3, 4}
>>> type(data)
<class 'set'>
>>> data = {1: 2, 3: 4}
>>> type(data)
<class 'dict'>

Empty dict and empty set:

>>> data = {1: 1}
>>> data.pop(1)
1
>>> data
{}
>>> data = {1}
>>> data.pop()
1
>>> data
set()

Differences:

>>> data = {1: 1}
>>> isinstance(data, set)
False
>>> isinstance(data, dict)
True
>>> data = {1}
>>> isinstance(data, set)
True
>>> isinstance(data, dict)
False
>>> data = {}
>>> isinstance(data, set)
False
>>> isinstance(data, dict)
True

6.1.10. Length

>>> crew = {
...    'commander': 'Melissa Lewis',
...    'botanist': 'Mark Watney',
...    'chemist': 'Alex Vogel'}
>>>
>>> len(crew)
3
>>> len(crew.keys())
3
>>> len(crew.values())
3
>>> len(crew.items())
3

6.1.11. Examples

>>> git = {
...    'ce16a8ce': 'commit/1',
...    'cae6b510': 'commit/2',
...    '895444a6': 'commit/3',
...    'aef731b5': 'commit/4',
...    '4a92bc79': 'branch/master',
...    'b3bbd85a': 'tag/v1.0'}

6.1.12. Assignments

Code 6.1. Solution
"""
* Assignment: Mapping Dict Define
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Create `result: dict` representing input data
    2. Non-functional requirements:
        a. Assignmnet verifies creation of `dict()`
        b. Do not parse `DATA`, simply model `result` based on `DATA`
        c. Do not use `str.split()`, `slice`, `getitem`, `for`, `while` or any other control-flow statement
    3. Run doctests - all must succeed

Polish:
    1. Stwórz `result: dict` reprezentujący dane wejściowe
    2. Wymagania niefunkcjonalne:
        a. Zadanie sprawdza tworzenie `dict()`
        b. Nie parsuj `DATA`, po prostu zamodeluj `result` bazując na `DATA`
        c. Nie używaj `str.split()`, `slice`, `getitem`, `for`, `while` lub jakiejkolwiek innej instrukcji sterującej
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> type(result)
    <class 'dict'>
    >>> 'First Name' in result.keys()
    True
    >>> 'Last Name' in result.keys()
    True
    >>> 'Missions' in result.keys()
    True
    >>> result['First Name'] == 'Jan'
    True
    >>> result['Last Name'] == 'Twardowski'
    True
    >>> 'Apollo' in result['Missions']
    True
    >>> 'Artemis' in result['Missions']
    True
"""

DATA = """
    First Name: Jan
    Last Name: Twardowski
    Missions: Apollo, Artemis
"""

result = ...  # dict[str,str|list]: with First Name, Last Name and Missions as keys


Code 6.2. Solution
"""
* Assignment: Mapping Dict Items
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Define `keys: list[str]` with list of `DATA` keys
    2. Define `values: list[str]` with list of `DATA` values
    3. Define `items: list[tuple]` with list of `DATA` key-value pairs
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `keys: list[str]` z listą kluczy z `DATA`
    2. Zdefiniuj `values: list[str]` z listą wartości z `DATA`
    3. Zdefiniuj `items: list[tuple]` z listą par klucz-wartość z `DATA`
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> type(keys)
    <class 'list'>
    >>> type(values)
    <class 'list'>
    >>> type(items)
    <class 'list'>
    >>> all(type(x) is tuple for x in items)
    True
    >>> keys
    ['Sepal length', 'Sepal width', 'Petal length', 'Petal width']
    >>> values
    [5.8, 2.7, 5.1, 1.9]
    >>> items  # doctest: +NORMALIZE_WHITESPACE
    [('Sepal length', 5.8),
     ('Sepal width', 2.7),
     ('Petal length', 5.1),
     ('Petal width', 1.9)]
"""

DATA = {
    'Sepal length': 5.8,
    'Sepal width': 2.7,
    'Petal length': 5.1,
    'Petal width': 1.9,
}

keys = ...  # list[str]: with keys from DATA
values = ...  # list[float]: with values from DATA
items = ...  # list[tuple]: with key-value pairs from DATA


Code 6.3. Solution
"""
* Assignment: Mapping Dict Get
* Required: no
* Complexity: easy
* Lines of code: 2 lines
* Time: 5 min

English:
    1. Create translator of pilot's alphabet
    2. Each letter has it's phonetic counterpart
    3. Ask user to input letter
    4. User will always put only one capitalized letter or number
    5. Define `result: str` with phonetic letter pronunciation
    6. If user type character not existing in alphabet, print: "Pilots don't say that"
    7. Do not use `if`, `try`, and `except`
    8. `MagicMock` will simulate inputting a letter by user
    9. Use `input()` function as normal
    10. Run doctests - all must succeed

Polish:
    1. Stwórz tłumacza alfabetu pilotów
    2. Pojedynczym literom przyporządkuj ich fonetyczne odpowiedniki
    3. Poproś użytkownika o wprowadzenie litery
    4. Użytkownik zawsze poda tylko jedną dużą literę lub cyfrę
    5. Zdefiniuj `result: str` z fonetyczną wymową litery
    6. Jeżeli wpisał znak, który nie występuje w alfabecie, wypisz: "Pilots don't say that"
    7. Nie używaj `if`, `try` ani `except`
    8. `MagicMock` zasymuluje wpisanie litery przez użytkownika
    9. Skorzytaj z funkcji `input()` tak jak normalnie
    10. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> result
    'Mike'
"""

# Simulate user input (for test automation)
from unittest.mock import MagicMock
input = MagicMock(side_effect=['M'])


ALPHABET = {
    'A': 'Alfa',
    'B': 'Bravo',
    'C': 'Charlie',
    'D': 'Delta',
    'E': 'Echo',
    'F': 'Foxtrot',
    'G': 'Golf',
    'H': 'Hotel',
    'I': 'India',
    'J': 'Juliet',
    'K': 'Kilo',
    'L': 'Lima',
    'M': 'Mike',
    'N': 'November',
    'O': 'Oscar',
    'P': 'Papa',
    'Q': 'Quebec',
    'R': 'Romeo',
    'S': 'Sierra',
    'T': 'Tango',
    'U': 'Uniform',
    'V': 'Victor',
    'W': 'Whisky',
    'X': 'X-Ray',
    'Z': 'Zulu',
}

letter = ...  # str: with letter from user
result = ...  # str: with converted letter to Pilot alphabet or "Pilots don't say that"

Code 6.4. Solution
"""
* Assignment: Mapping Dict Translate
* Required: no
* Complexity: easy
* Lines of code: 2 lines
* Time: 5 min

English:
    1. Ask user to input single letter
    2. Convert to lowercase
    3. If letter is in `PL` then use conversion value as letter
    4. `MagicMock` will simulate inputting of a letter by user
    5. Use `input()` function as normal
    6. Run doctests - all must succeed

Polish:
    1. Poproś użytkownika o wprowadzenie jednej litery
    2. Przekonwertuj literę na małą
    3. Jeżeli litera jest w `PL` to użyj skonwertowanej wartości jako litera
    4. `MagicMock` zasymuluje wpisanie litery przez użytkownika
    5. Skorzytaj z funkcji `input()` tak jak normalnie
    6. Uruchom doctesty - wszystkie muszą się powieść

Example:
    | Input | Output |
    |-------|--------|
    |   A   |    a   |
    |   x   |    x   |
    |   Ł   |    ł   |
    |   ś   |    s   |
    |   Ź   |    z   |

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> import string

    >>> type(result)
    <class 'str'>
    >>> result not in PL.keys()
    True
    >>> result in string.ascii_letters
    True
"""

# Simulate user input (for test automation)
from unittest.mock import MagicMock
input = MagicMock(side_effect=['Ł'])


PL = {
    'ą': 'a',
    'ć': 'c',
    'ę': 'e',
    'ł': 'l',
    'ń': 'n',
    'ó': 'o',
    'ś': 's',
    'ż': 'z',
    'ź': 'z',
}

letter = ...  # str: with letter from user
result = ...  # str: with converted letter without PL diacritic chars


6.1.13. References

1

Frédéric Hamidi. Why does Python 3 need dict.items to be wrapped with list()? Retrieved: 2021-02-28. URL: https://stackoverflow.com/a/17695716