Ash Bellett
Ash Bellett

Reputation: 13

How to create a dictionary of dictionaries using dict comprehension?

I am trying to create a dictionary of dictionaries using a dict comprehension over a list of lists.

The list of lists contains student names, assignment names and grades. For example, the data could look like this:

grades_lists = [
    ['Student', 'Test', 'Assignment', 'Exam'],
    ['Bella', '80', '90', '100'],
    ['Sam', '65', '75', '85'],
    ['Emily', '60', '75', '90'],
]

I want to create a dictionary that maps the student names to dictionaries containing the assignment name and grade. For example:

grades_dicts['Bella']['Test'] == 80

The dictionary I would like is this:

{
    'Bella': {'Test': 80, 'Assignment': 90, 'Exam': 100},
    'Sam': {'Test': 65, 'Assignment': 75, 'Exam': 85},
    'Emily': {'Test': 60, 'Assignment': 75, 'Exam': 90}
}

so each row in grades_list becomes a dictionary; the first column is the name of the student, the other columns are values in a dictionary whose keys are taken from the first row from grades_list.

I have been trying to use dict comprehension to generate the corresponding keys and values in the dictionaries. Something like this:

grade_dicts = {x[0]:{y:z} for y in grades_lists[0][1:] for x in grades_lists[1:] for z in x[1:]}

But this does not generate all of the grades correctly:

{
    'Bella': {'Exam': '100'},
    'Emily': {'Exam': '90'},
    'Sam': {'Exam': '85'}
}

Upvotes: 1

Views: 5119

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121524

You got your nesting mixed up. The following works:

{name: {col: int(value) for col, value in zip(grades_list[0][1:], row)}
 for name, *row in grades_list[1:]}

The for name, *row in ... loop takes each row, and extracts just the first value into name, the remainder into row (the * tells Python to take the remainder). This lets you then use the row together with the column names from the first row to create the nested dictionaries.

It is often easier to see what a comprehension does by using regular loops. Your code does this:

grade_dicts = {}
for y in grades_lists[0][1:]:
    for x in grades_lists[1:]:
        for z in x[1:]:
            grade_dicts[x[0]] = {y:z}

That's way too many loops, and you end up setting a value for x[0] several times; len(x[1:]) times in fact. Only the last {y: z} dictionary remains.

My version does this:

grade_dicts = {}
for name, *row in grades_list[1:]:
    inner = {}
    for col, value in zip(grades_list[0][1:], row):
        inner[col] = int(value)
    grade_dicts[name] = inner

That's just two loops, where the inner produces a separate dictionary for each column. I used zip() to pair up the labels and the values.

Upvotes: 5

Related Questions