Mixed structures#

In this notebook we will see how to manage more complex data structures like lists of dictionaries and dictionaries of lists, examining also the meaning of shallow and deep copy.

Since some of the functions you’ll need to test return a float, we can’t compare for exact numbers but only for close numbers. So we’ll need the math.isclose() function for that. Let’s then first import math:

import math

Exercise - Luxury Holding#

A luxury holding groups several companies and has a database of managers as a list of dictionaries. Each employee is represented by a dictionary:

{
    "name":"Alessandro",
    "surname": "Borgoloso",
    "age": 34,
    "company": {
                   "name": "Candied Herrings",
                   "sector":"Food"
               }
}

The dictionary has several simple attributes like name, surname, age. The attribute company is more complex, because it is represented as another dictionary:

    "company": {
                   "name": "Candied Herrings",
                   "sector":"Food"
               }
managers_db = [
    {
        "name": "Alessandro",
        "surname": "Borgoloso",
        "age": 34,
        "company": {"name": "Candied Herrings", "sector": "Food"},
    },
    {
        "name": "Matilda",
        "surname": "Delle Sòle",
        "age": 25,
        "company": {"name": "Pythonic Footwear", "sector": "Fashion"},
    },
    {
        "name": "Alfred",
        "surname": "Pennyworth",
        "age": 20,
        "company": {"name": "Batworks", "sector": "Fashion"},
    },
    {
        "name": "Arianna",
        "surname": "Schei",
        "age": 37,
        "company": {"name": "MegaDiamonds Unlimited", "sector": "Precious stones"},
    },
    {
        "name": "Antonione",
        "surname": "Cannavacci",
        "age": 25,
        "company": {"name": "Pre-chewed Chewing gums", "sector": "Food"},
    },
]

Exercise - extract_managers#

✪✪ Define a function extract_managers that returns the manager names in a list

Hide code cell source
def extract_managers(db):
    ret = []
    for d in db:
        ret.append(d["name"])
    return ret
assert extract_managers([]) == []

# if it doesn't find managers_db, remember to execute the cell above which defines it !
assert extract_managers(managers_db) == ['Alessandro', 'Matilda', 'Alfred', 'Arianna', 'Antonione']

Exercise - extract_companies#

✪✪ Define a function extract_companies that returns the names of departments in a list.

Hide code cell source
def extract_companies(db):
    ret = []
    for d in db:
        ret.append(d["company"]["name"])
    return ret
assert extract_companies([]) == []
# if it doesn't find managers_db, remember to execute the cell above which defines it !
assert extract_companies(managers_db) == ["Candied Herrings","Pythonic Footwear","Batworks","MegaDiamonds Unlimited","Pre-chewed Chewing gums"]

Exercise - avg_age#

✪✪ Define a function avg_age that returns the average age of managers

Hide code cell source
def avg_age(db):
    s = 0
    for d in db:
        s += d["age"]
    
    return s / len(db)
assert math.isclose(avg_age(managers_db), (34 + 25 + 20 + 37 + 25) / 5)

Exercise - sectors#

✪✪ Define a function sectors that returns the company sectors in a list, WITHOUT duplicates and alphabetically sorted!!!

Hide code cell source
def sectors(db):
    ret = []
    for d in db:
        sector = d["company"]["sector"]
        if sector not in ret:
            ret.append(sector)

    ret.sort()
    return ret
assert sectors([]) == []
assert sectors(managers_db) == ["Fashion", "Food", "Precious stones"]

Exercise - averages#

✪✪ Define a function averages that, given a dictionary structured as a tree regarding the grades of a student in class V and VI, returns an array containing the average for each subject

Hide code cell source
def averages(lst):
    ret = [0.0, 0.0, 0.0]
    
    for i in range(len(lst)):
        ret[i] = (lst[i]['V'] + lst[i]['VI']) / 2
   
    return ret

Example result:

data = [
    {"id": 1, "subject": "math", "V": 70, "VI": 82},
    {"id": 1, "subject": "italian", "V": 73, "VI": 74},
    {"id": 1, "subject": "german", "V": 75, "VI": 86},
]
averages(data)
[76.0, 73.5, 80.5]
def is_list_close(lista, listb):
    """Verifies the float numbers in lista are similar to numbers in listb"""

    if len(lista) != len(listb):
        return False

    for i in range(len(lista)):
        if not math.isclose(lista[i], listb[i]):
            return False

    return True


assert is_list_close(averages(data), [76.0, 73.5, 80.5])

Exercise - has_pref#

✪✪ A big store has a database of clients modelled as a dictionary which associates customer names to their preferences regarding the categories of articles the usually buy:

    {
        'aldo':     ['cinema', 'music', 'sport'],
        'giovanni': ['music'],
        'giacomo':  ['cinema', 'videogames']
    }

Given the dictionary, the customer name and a category, write a function has_pref which returns True if that client has the given preference, False otherwise

Hide code cell source
def has_pref(d, name, pref):
    if name in d:
        return pref in d[name]
    else:
        return False

Example result:

data = {
    "aldo": ["cinema", "music", "sport"],
    "giovanni": ["music"],
    "giacomo": ["cinema", "videogames"],
}
# Will returns `True`, because also likes music:
print(has_pref(data, "aldo", "music"))
# Will return `False`, because giacomo doesn't like sport:
print(has_pref(data, "giacomo", "sport"))
True
False
assert not has_pref({}, "a", "x")
assert not has_pref({"a": []}, "a", "x")
assert has_pref({"a": ["x"]}, "a", "x")
assert not has_pref({"a": ["x"]}, "b", "x")
assert has_pref({"a": ["x", "y"]}, "a", "y")
assert has_pref({"a": ["x", "y"], "b": ["y", "x", "z"]}, "b", "y")
assert has_pref(data, "aldo", "music")
assert not has_pref(data, "giacomo", "sport")

Exercise - festival#

✪✪✪ During a country festival in Italy, the local pastry shops decide to donate each a certain amount of pastries. Every shop is represented as a dictionary, which contains pastries names as keys, plus the special key name which represents the shop name itself (assume all the shops produce the same types of pastries), like in the two examples given below:

shops1 = [
    {"name": "La Patisserie", "cornetti": 2},
    {"cornetti": 5, "name": "La Casa Del Cioccolato"},
]
shops2 = [
    {"babbà": 3, "bignè": 4, "zippole": 2, "name": "Da Gigi"},
    {"babbà": 5, "bignè": 3, "zippole": 9, "name": "La Delizia"},
    {"babbà": 1, "bignè": 2, "zippole": 6, "name": "Gnam gnam"},
    {"babbà": 7, "bignè": 8, "zippole": 4, "name": "Il Dessert"},
]

Given a list of such dictionaries and a list of pastries pastries, we want to produce as output a NEW list of lists, which has the totals of each pastry type, as in the example below:

Hide code cell source
def festival(shops, pastries):
    ret = []
    # we make a copy of pastries to prevent modification of the input
    ret.append(["Name"] + pastries[:])
    sums = [0] * (len(pastries) + 1)
    sums[0] = "Totals"
    for p in shops:
        j = 1
        row = [p["name"]]
        for pastry in pastries:
            row.append(p[pastry])
            sums[j] += p[pastry]
            j += 1
        ret.append(row)
    ret.append(sums)
    return ret
festival(shops2, ["bignè", "zippole", "babbà"])
[['Name', 'bignè', 'zippole', 'babbà'],
 ['Da Gigi', 4, 2, 3],
 ['La Delizia', 3, 9, 5],
 ['Gnam gnam', 2, 6, 1],
 ['Il Dessert', 8, 4, 7],
 ['Totals', 17, 21, 16]]
pastries1 = ["cornetti"]
res1 = festival(shops1, pastries1)
assert res1 == [
    ["Name", "cornetti"],
    ["La Patisserie", 2],
    ["La Casa Del Cioccolato", 5],
    ["Totals", 7],
]
assert pastries1 == ["cornetti"]  # verify the input didn't change

res2 = festival(shops2, ["bignè", "zippole", "babbà"])

assert res2 == [
    ["Name", "bignè", "zippole", "babbà"],
    ["Da Gigi", 4, 2, 3],
    ["La Delizia", 3, 9, 5],
    ["Gnam gnam", 2, 6, 1],
    ["Il Dessert", 8, 4, 7],
    ["Totals", 17, 21, 16],
]

Exercise - actorswap#

✪✪✪ Given a movie list movies where each movie is represented as a dictionary, RETURN a NEW list with NEW dictionaries having the male actor names swapped with the female ones.

  • ONLY swap actor names

  • you can’t predict actor names

  • you only know each dictionary holds exactly three keys, of which these two are known: title and year.

For example, with the following data, you should obtain the result shown below:

films1 = [
    {"title": "Pretty Woman", "year": 1990, "Edward": "Vivian"},
    {"title": "Titanic", "year": 1997, "Jack": "Rose"},
]
films2 = [
    {
        "title": "Jerry Maguire",
        "year": 1996,
        "Jerry": "Dorothy",
    },
    {
        "title": "Superman",
        "Kent": "Lois",
        "year": 1978,
    },
    {
        "title": "The Lord of the Rings",
        "year": 2001,
        "Aragorn": "Arwen",
    },
    {
        "Ron Weasley": "Hermione",
        "title": "Harry Potter and the Deathly Hallows, Part 2",
        "year": 2011,
    },
]
Hide code cell source
def actorswap(movies):
    ret = []
    for diz in movies:
        nuovo = {}
        ret.append(nuovo)
        for k in diz:        
            if k == 'title' or k == 'year':
                nuovo[k] = diz[k]
            else:
                nuovo[diz[k]] = k        
    return ret
actorswap(films2)
[{'title': 'Jerry Maguire', 'year': 1996, 'Dorothy': 'Jerry'},
 {'title': 'Superman', 'Lois': 'Kent', 'year': 1978},
 {'title': 'The Lord of the Rings', 'year': 2001, 'Arwen': 'Aragorn'},
 {'Hermione': 'Ron Weasley',
  'title': 'Harry Potter and the Deathly Hallows, Part 2',
  'year': 2011}]
assert actorswap([]) == []

orig_film = films1[0]
res2 = actorswap(films1)
assert res2 == [
    {"title": "Pretty Woman", "year": 1990, "Vivian": "Edward"},
    {"title": "Titanic", "year": 1997, "Rose": "Jack"},
]
assert id(films1) != id(res2)  # must produce a NEW list
assert id(orig_film) != id(res2[0])  # must produce a NEW dictionary

assert actorswap(films2) == [
    {"title": "Jerry Maguire", "year": 1996, "Dorothy": "Jerry"},
    {"title": "Superman", "year": 1978, "Lois": "Kent"},
    {"title": "The Lord of the Rings", "year": 2001, "Arwen": "Aragorn"},
    {
        "title": "Harry Potter and the Deathly Hallows, Part 2",
        "year": 2011,
        "Hermione": "Ron Weasley",
    },
]