Skip to content
The AI Agent Factory

13.6 Lists and dictionaries

Approved by
Panaversity ·
13 min read

In everyday life. A grocery list is in order: first milk, then bread. You find items by position. A coat check is different: you get your coat back by its ticket number, and a ticket they never gave out gets you nothing. Each has a trap: a list can come out in the wrong order, and a lookup can find nothing.

What you will learn. The two questions to ask of every sort and every lookup: what does it compare, and what if the name is missing?

Why it matters. A count and a total cannot see a wrong order or a missing name.

List or dictionary?

A list is an ordered collection, where you find each item by its position. TypeScript calls it an array. A dictionary holds values under names, called keys, so you find a value by its key. TypeScript uses an object, written with braces, for the same job, and Python calls it a dict. A record, such as one invoice with its number, vendor and amount, is a dictionary too.

List (array)Dictionary (object, dict)
Found byPosition, starting at 0Key, such as "V005"
Brightline useThe open invoices, in orderVendor names by vendor id
Written as["INV-8301", "INV-8305"]{ V005: "Hocking Valley Pallets" }

Records become JSON. Most of the data a program sends or receives is a list of dictionaries, one for each record. Chapter 14 reads invoices from an API, which is a way for one program to ask another for data. Each invoice arrives as a dictionary, in a text format called JSON. Reading this shape well helps in every later chapter.

What does a sort compare, and why can't my count and total see it?

It compares what its rule tells it to compare. With no rule, it uses a default you must know. Sorting is where Renee's script went wrong, and the two languages behave differently. Read every sort line an agent writes, and ask what it compares.

At work. Ana still has the check she wrote in Chapter 12: Friday's 8 invoices, $19,229.53. The controller asks for Friday's list, largest first. The agent sorts it, the tests pass, and its summary matches her check. Before she has the agent run the list, she reads the one line that changed.

.sort((a, b) => String(b.cents).localeCompare(String(a.cents)));

The line turns each amount in cents into text with String(...). Then localeCompare puts two pieces of text in order. She writes in NOTES.md: "It compares the amounts as text." As text, "61875" is larger than "482500", because its first character, 6, is larger than 4. Length does not count, just as "bus" comes after "airplane" in a dictionary. On Friday's list, only INV-8305's 735000 starts with a larger digit. So she predicts that INV-8302, Buckeye Janitorial Supply, at 61875 cents, will come second.

$ npm run due -- 2027-03-05
2027-03-05  INV-8305  Hocking Valley Pallets  $7,350.00
2027-03-03  INV-8302  Buckeye Janitorial Supply  $618.75
2027-03-03  INV-8301  Midwest Packaging Co.  $4,825.00
2027-03-01  INV-8307  Ohio Valley Electric  $2,411.33
2027-02-26  INV-8304  Scioto Office Products  $234.50
2027-03-03  INV-8314  Lakefront Labels Inc.  $2,265.00
2027-03-05  INV-8312  Scioto Office Products  $199.95
2027-03-04  INV-8315  Cardinal Forklift Service  $1,325.00
8 invoices, total $19,229.53

It does. Her check named a count and a total, never an order, so only her sentence caught it. She lists amounts of different lengths, one of them seven digits, in the order they must come out. The agent writes that test, and she sees it fail on the old line. Then she has the agent compare the numbers, b.cents - a.cents, and the test passes. Next Friday's cutoff is 2027-03-12. Left as it was, the line would put INV-8303, Great Lakes Freight's $12,900.00, the largest open invoice, below INV-8312's $199.95. It is Renee's mistake, one week later.

What happens to a missing key?

TypeScript gives undefined and keeps running. Python stops with a KeyError. A key must match exactly, capital letters too, so v005 is not V005. A position past the end of a list is missing in the same way: undefined in TypeScript, an IndexError in Python.

At work. Brightline's export names each vendor by id, such as V005, not by name. To print names, the script builds a dictionary from vendors.csv, then looks up each invoice's vendor id. Ana asks one question about that code. What happens when an invoice names a vendor id that is not in the dictionary? Chapter 14's messy file has one, V099.

In TypeScript. Record<string, string> is an object whose keys and values are text. Predict the first three lines. The third one is the trap.

const names: Record<string, string> = { V001: "Midwest Packaging Co.", V005: "Hocking Valley Pallets" };
console.log(names["V005"]);
console.log(names["V099"]);
const cents = [735000, 482500, 61875];
console.log(cents.sort());
console.log(cents.sort((a, b) => a - b));
Hocking Valley Pallets
undefined
[ 482500, 61875, 735000 ]
[ 61875, 482500, 735000 ]

A missing key gives undefined, with no error. And sort() with no rule compares the values as text, so 482500 comes before 61875. That is Renee's bug, in one line. The last line gives the rule, (a, b) => a - b. The sort hands the rule two items at a time, and a negative answer puts a first. For 61875 and 482500, a - b is -420625, so 61875 goes first. That makes a - b smallest first. b - a is largest first, as in Ana's b.cents - a.cents.

In Python.

names: dict[str, str] = {"V001": "Midwest Packaging Co.", "V005": "Hocking Valley Pallets"}
print(names["V005"])
print(names.get("V099"))
cents: list[int] = [735000, 482500, 61875]
print(sorted(cents))
print(sorted(cents, key=str))
print(names["V099"])
Hocking Valley Pallets
None
[61875, 482500, 735000]
[482500, 61875, 735000]
Traceback (most recent call last):
  ...
KeyError: 'V099'

.get() gives None for a missing key, or a default you pass it, as in .get(key, 0). sorted compares numbers as numbers. Its rule goes in key, which says what to compare each item by, so key=str compares them as text and brings Renee's bug back on purpose. In agent code you will often see key=lambda v: .... Read what follows the colon: that is what it compares. reverse=True runs a sort largest first.

What are sets, tuples and slices?

Three more shapes you will see in agent code. Neither question above needs them, so read this table to recognize them when they appear.

ShapeWhat it isTypeScriptPython
A setHolds each value once, with no positions, like a guest list. It finds a duplicate: the set comes out smaller than the listnew Set(ids), .has(x), .sizeset(ids), x in s, len(s)
A tupleA short, fixed group of values, such as a vendor id and an amountAn array with a tuple type, [string, number], which can still change("V005", 735000), which cannot change
A slicePart of a list or a string, by position. From 0 to 2 takes positions 0 and 1, because the end is left out, like a hotel stay from the 1st to the 3rd: two nights. A negative number counts from the endslice(0, 2), slice(-1)[0:2], [-1:]

In Python, vendor_id, cents = pair unpacks a tuple: it puts the tuple's two values into two variables. You will see this line often.

Figure 13.7. Four invoice numbers at positions 0 to 3, with INV-8301 twice. A bracket marks the slice from 0 to 2 over positions 0 and 1, with position 2 marked as the end, left out. Another marks the last item, position 3. Below, the set holds three values with the duplicate dropped, and a tuple ("V005", 735000) that cannot be changed in Python.

Figure 13.7. A slice takes positions up to its end, never the end itself. A set keeps each value once. A tuple is fixed.

Where the languages differ.

TypeScriptPython
A missing keyundefined, and the program keeps runningKeyError, unless you use .get()
Sorting numbers with no ruleCompares them as textCompares them as numbers
A sort's rule(a, b) => a - bkey=..., and reverse=True for largest first
Total of a listreduce((sum, x) => sum + x, 0), a running total that starts at 0sum(...)
A new list from each itemmap((inv) => inv.cents)[inv["cents"] for inv in invoices]

The operating rule: for every lookup, ask what happens when the key is missing, and for every sort, ask what it compares.

Figure 13.8. The INV-8301 row of invoices.csv becoming a record. The header names become keys, the cells become values, and the amount "482500" is converted from text to the whole number 482500. Blank or invalid amounts are rejected.

Figure 13.8. A row becomes a record. The header gives the keys. Every value starts as text until the code converts it.

Practice it. Practice: lists and dictionaries, in your own tool, fifteen minutes.

Check yourself

Question 1 / 10

0 answered

The vendor id comes from a file that someone typed by hand.const terms: Record<string, string> = { V031: "Net 30", V032: "Net 15" } const fromFile = "v031" console.log(terms[fromFile])What does it print?

Owner
Panaversity