Records: lists of dicts and dicts of lists

Part of the free LogicWiz Python course, Module 6: Dictionaries and sets. The examples and practice run in the app.

The board practical says: "Store the names and marks of n students and display the names of students who scored above 75." With one dict per student, that is a three-line loop.

You'll learn to

  • Store each record as a dict, and a table as a list of dicts
  • Use a dict of lists when you think in columns
  • Group with `setdefault`, start counters with `fromkeys`, and sort records by a field

Learn

One dict per record

A record is everything about one thing: one student, one order. A dict fits it well, because each field gets a name:

{"name": "Aarav", "section": "A", "marks": 82}

A whole class is then a list of dicts, one dict per student. Reading s["marks"] is much clearer than remembering that marks were at index 2 of a list.

Students with marks above 75

students = [
    {"name": "Aarav", "marks": 82},
    {"name": "Meher", "marks": 71},
    {"name": "Riya", "marks": 94},
]
for s in students:
    if s["marks"] > 75:
        print(s["name"], s["marks"])

Dict of lists: one list per column

marks = {"Maths": [78, 66, 88], "CS": [90, 58, 95]}
for subject, scores in marks.items():
    print(subject, max(scores), round(sum(scores) / len(scores), 1))
marks["CS"].append(70)
print(marks["CS"])

Here the key is a subject and the value is a whole list of scores. Good when you work column by column (this is also how a pandas table is built, in Module 10).

Which shape?

List of dictsDict of lists
[{"name": "Aarav", "marks": 82}, …]{"name": ["Aarav", …], "marks": [82, …]}
one item per row (record)one item per column (field)
easy to add, filter or sort studentseasy to total or average a whole column
students[0]["marks"]table["marks"][0]

Records by roll number

db = {101: {"name": "Aarav", "marks": 82},
      102: {"name": "Meher", "marks": 71}}
print(db[102]["name"])
db[101]["marks"] += 5
print(db[101])
print(103 in db)

A dict of dicts gives you instant lookup by roll number, then a field by name: db[roll][field].

setdefault and fromkeys

pairs = [("Aarav", "A"), ("Meher", "B"), ("Riya", "A")]
sections = {}
for name, sec in pairs:
    sections.setdefault(sec, []).append(name)
print(sections)

att = dict.fromkeys(["Aarav", "Meher", "Riya"], 0)
att["Meher"] += 1
print(att)

setdefault(key, default) returns the value for key, first storing default if the key is missing. Perfect for grouping. dict.fromkeys(keys, value) makes a dict with every key set to the same starting value.

Sorting records by a field

students = [{"name": "Aarav", "marks": 82},
            {"name": "Meher", "marks": 71},
            {"name": "Riya", "marks": 94}]

def by_marks(s):
    return s["marks"]

for s in sorted(students, key=by_marks, reverse=True):
    print(s["name"], s["marks"])

key= takes a function that turns each record into the value to sort by. Python calls by_marks on each dict and sorts by what it returns.

dict.fromkeys(["x", "y"], []) puts the same empty list under every key (the aliasing trap again), so appending to one appends to all. Use fromkeys only with values you won't change in place, like 0 or "", and use setdefault(k, []) for lists.

CBSE Class 11/12 practical: "Input the names and marks of n students into a dictionary and display the names of students with marks above 75." Read n, loop n times with input(), store record[name] = marks, then loop over the dict and print the ones that pass the test.

In one line

A record is a dict, a table is a list of dicts; group with d.setdefault(k, []).append(x); use fromkeys only with immutable starting values.