Organizing Data

Collections of Records


Learning Objectives

  • You can combine a list with dictionaries to represent several records.
  • You can search, filter, and update records using functions.
  • You can explain which objects a returned collection shares with its input.
  • You can copy flat records and test the promised independence.

One dictionary can describe one session. A list of dictionaries can describe several sessions:

sessions = [
{"subject": "Programming", "minutes": 25},
{"subject": "Math", "minutes": 40},
{"subject": "Programming", "minutes": 15}
]

The outer list preserves the session order. Each dictionary keeps a subject and a duration together. The two levels have different jobs.

Follow One Access at a Time

Consider:

first_session = sessions[0]
print(first_session["subject"])

sessions[0] retrieves a dictionary. Looking up "subject" in that dictionary retrieves a string. The shorter expression does the same two steps:

print(sessions[0]["subject"])

Read nested access from left to right. Do not treat two sets of brackets as an entirely new operation.

Reuse Familiar Loops

Direct iteration now produces records:

def total_minutes(sessions):
total = 0
for session in sessions:
total = total + session["minutes"]
return total

The total pattern has not changed. The loop obtains the relevant number from a field rather than receiving that number directly.

Assume each record has a "subject" string and a positive integer "minutes". A function can state that input contract without checking every type on every call. Part 5 will examine validation when records come from files rather than controlled examples.

Now write a function that selects session subjects using their durations. Follow the same two steps: read a record from the list, then look up the fields you need.

List session subjects

0 / 15 points

Write subjects_for_long_sessions(sessions, minimum). Each record has a subject string and positive integer minutes. The minimum is a nonnegative integer. Return a new list of subjects for sessions lasting at least the minimum, preserving order and repeated subjects. Return [] for no matches. Do not print or change the records.

For [{"subject":"A","minutes":30},{"subject":"B","minutes":10},{"subject":"A","minutes":40}] and 30, return ["A", "A"].

Find a Record by Its Identifier

Position can change when a list is reordered. An identifier gives a record a name that need not change with its position:

items = [
{"id": "E10", "name": "Camera", "available": True},
{"id": "E20", "name": "Tripod", "available": False}
]
def find_item(items, item_id):
for item in items:
if item["id"] == item_id:
return item
return None

The contract assumes unique identifiers. It returns the matching dictionary itself, or None when no item matches. It does not create a copy.

This matters:

found = find_item(items, "E10")
if found != None:
found["available"] = False
assert items[0]["available"] == False

The local name and the list item refer to the same dictionary. This is the aliasing from Part 3, now inside a collection.

Update the catalogue

0 / 15 points

Write mark_available(items, item_id). Items are records with unique id strings, name strings, and available booleans. Identifiers are case-sensitive. If an item with the exact identifier exists, set its available field to True and return True, including when it was already available. If no item matches, return False and change nothing. Update the existing dictionary so that other names for it observe the change. Preserve every other field and record, including any extra fields. Do not print.

A New List May Still Contain Existing Records

This function creates a new list, but not new records:

def select_long_sessions(sessions, minimum):
result = []
for session in sessions:
if session["minutes"] >= minimum:
result.append(session)
return result

Appending or removing an item in result does not add or remove an item in sessions. Updating a dictionary inside result, however, changes a dictionary that both lists can reach.

A new outer list does not imply independent contents.

That is not automatically a bug. It depends on the interface promised by the function.

Here is the filtering loop with two input records and a minimum of 30 minutes. Follow the reference appended to result: updating that record is visible through sessions, but appending another record changes only the result list.

Code

Step 1 of 13

1 / 13
Formatting...

State

main

No variables

Objects

No objects yet

Output

No output yet

In the following questions, distinguish changes to a list from changes to a record inside it. Follow which access paths reach the same dictionary before deciding what a caller can observe.

Copy a Flat Record When Independence Is Required

The dictionary method .copy() creates a new dictionary with the same entries:

No files opened Select a file to edit

Here all values are strings or integers. Replacing a value in copy does not replace the corresponding value in original.

A filtering function that promises independent flat records can append a copy:

result.append(session.copy())

This is a shallow copy: the outer dictionary is new, but its values are the same objects. That distinction will become important when a field contains a list in the next chapter.

Prepare session copies

0 / 15 points

Write copy_long_sessions(sessions, minimum). Records contain exactly a subject string and positive integer minutes; minimum is a nonnegative integer. Select sessions lasting at least minimum, preserving order and repeated records. Return a new list containing a new dictionary for every selected session. Changing the result or any returned record must not change the input. Return [] for no matches. Do not print.

For one 25-minute session and minimum 20, the result initially has equal contents to the input, but changing the returned minutes must leave the input at 25.

Explain which objects each result shares with its input, and show how changing a returned record reveals the difference.

Explain a selection

0 / 10 points

The list records contains at least two dictionaries. Each dictionary contains only a "name" string and integer "points". One function returns [records[1]]; another returns [records[1].copy()]. In a short explanation, compare appending to each returned list and changing the returned record’s points. Give an example with expected original and returned values, and explain which version fits a caller that needs independently editable records.

Check Your Understanding

  1. What does the loop variable hold when iterating over a list of records?
  2. Why might an identifier be more useful than a list position?
  3. What does find_item return when it finds a match?
  4. Why can a new filtered list still share records with its input?
  5. What extra test checks independence rather than only equal contents?