Skip to content

← Algorithms and programming

Python: Lists, Strings and Dictionaries

Unit 3 · Algorithms and programming

Hide the stories, facts, photos, activities and interactives, for revising before an exam
In this unit 11 sections
  1. Lists: many values in order
  2. Working through a list
  3. Strings: text, a character at a time
  4. Words and lines: split and join
  5. Dictionaries: looking things up by name
  6. Tables: rows and columns
  7. Choosing a data structure
  8. Reading code with lists and dictionaries
  9. Project
  10. In short
  11. Exercise

Open the contacts on a phone: hundreds of names, each with a number, and you find one by typing a few letters of the name. Open a mobile wallet’s statement: a long line of payments, newest first. Open the Brihat Shabdakosh: a word, then its meaning, tens of thousands of times over. Look at a class register: a row for each student, a column for each subject. None of these is one value. Each is a lot of values, held in a particular shape, and the shape is what makes the job easy: a statement is read in order, a contact is found by name, a register is read across a row or down a column.

In Grade 8 you kept one value in each variable, and met lists only as far as searching and sorting needed. Real programs handle hundreds, thousands or millions of values. In this unit you will learn Python’s three ways of holding many values at once: lists, kept in order and numbered; strings, which are lists of characters; and dictionaries, which find a value by its name. You will count words in a Nepali or English paragraph, read a table of Nepal’s provinces from a file and answer questions about it, and, most important of all, learn to choose the right shape for a job, because once the data is held the right way, the rest of the program is easy.

Lists: many values in order

In Grade 8 you met the list: values in square brackets, separated by commas, each item at its position (its index), counting from 0. len() gives the number of items, so the positions run from 0 to len(list) - 1, and a position past the end stops the program with an IndexError. The word in asks whether a value is in the list. Here is all of that in four lines, to remind you:

marks = [52, 38, 71, 45, 60]print(marks[0], marks[4])print(len(marks))print(38 in marks, 99 in marks)

Output

52 605True False

A list can hold any values: numbers, strings, True and False, even other lists (you will need that for tables later in this unit). It can hold the same value twice, and it keeps its items in the order you put them in.

Counting from the end

Python also numbers the items from the back, with minus numbers: marks[-1] is the last item, marks[-2] the one before it, and so on back to marks[-5], the first. So the last item is always marks[-1], however long the list, with no len() to work out.

marks = [52, 38, 71, 45, 60]print(marks[-1])print(marks[-2])print(marks[len(marks) - 1])

Output

604560

Try it

Two numbers for every position

Move the slider. Every item has two positions: one counted from the front, above it, and one from the back, below it. What happens one place past each end?

5238714560
marks[1] → 38 also marks[-4]

Counting from the front, the first is at position 0.

A minus position counts from the back: −1 is the last.

There is nothing at this position: the last is at len − 1, so Python stops with an IndexError.

There is nothing at this position: from the back, the first is at −len, so Python stops with an IndexError.

What you should see

Going through the items

In Grade 8 you went through a list by its positions: for i in range(len(marks)):, then marks[i]. When you only need the items, not where they are, a for loop can take the items themselves, one at a time:

names = ["Sita", "Bikash", "Gita"]for name in names:    print("Namaste,", name)

Output

Namaste, SitaNamaste, BikashNamaste, Gita

Each time round, the loop variable name holds the next item: first "Sita", then "Bikash", then "Gita". It is shorter, and there is no position to get wrong. Use range(len(…)) only when you need the position itself: to print it, to look at the item after it (as bubble sort did), or to change the item in the list.

Adding and removing items

A list can grow and shrink while the program runs. A tea shop keeps its orders in a list: new ones go on the end, and the one at the front is made first. A list has its own functions for this, written after the list’s name with a dot, like .lower() for strings in Grade 8. They are called methods.

Method What it does orders after it, from ["tea", "momo"]
orders.append("chiya") puts an item on the end ["tea", "momo", "chiya"]
orders.insert(0, "coffee") puts an item at a position; the rest move along ["coffee", "tea", "momo"]
orders.pop() takes out the last item and gives it back ["tea"] (gives back "momo")
orders.pop(0) takes out the item at a position and gives it back ["momo"] (gives back "tea")
orders.remove("momo") takes out the first item equal to this value ["tea"]
orders.index("momo") gives back the position of the first item equal to it; the list doesn’t change (gives back 1)
orders.count("tea") gives back how many items equal it (gives back 1)
orders.sort() sorts the list itself (Grade 8: it gives back None) ["momo", "tea"]

To change an item, set its position, as in Grade 8: orders[1] = "veg momo". To take out an item by its position without keeping it, del orders[1] works too.

Try it

A tea shop’s orders

Step through it and watch the list. Which line makes every item move one place along? Which gives something back?

orders = []orders.append("tea")orders.append("momo")orders.append("tea")orders.insert(0, "milk tea")served = orders.pop(0)orders.remove("momo")print(served, "served;", len(orders), "orders left")
Step 1 / 8

orders is an empty list: [] with nothing inside. Its length is 0.

Variables

ordersserved
[]–

Output

 
Answer

Building a list as you go

Often a list starts empty, [], and a loop fills it with append(), just as a running total starts at 0 and a loop adds to it. Here a teacher types four marks, and the program keeps every one of them:

marks = []for i in range(4):    m = int(input("Marks: "))    marks.append(m)print(marks)print(len(marks), "marks kept")

Output

Marks: 72Marks: 65Marks: 80Marks: 58[72, 65, 80, 58]4 marks kept

In Grade 8’s five-subject program, each new mark replaced the last one in the variable, and only the total survived. Now every mark is kept, so the program can use them again later: sort them, find the second highest, or print them in a report.

queue = ["Asha", "Bimal", "Chhiring"]. After queue.pop(0) and then queue.append("Dolma"), what is the list?

Pick an answer to see why.

names = ["Sita", "Ram", "Gita", "Hari"]. What is names[-2]?

Pick an answer to see why.

Working through a list

Once values are in a list, a short loop can answer almost any question about them. You know the patterns from Grade 8: a running total, a counter, the highest so far. On a list, add one more: filtering, making a new list of only the items that pass a test.

Here is a week of rain at a weather station near Pokhara, in millimeters (made-up numbers), with a running total and a filter in the same loop:

rain = [0, 12, 30, 0, 5, 0, 18]total = 0wet = []for r in rain:    total = total + r    if r > 10:        wet.append(r)print("Total:", total, "mm")print("Days over 10 mm:", wet)

Output

Total: 65 mmDays over 10 mm: [12, 30, 18]

Try it

A total and a filter in one loop

Step through it. Watch wet grow only on some rounds, and total on every round.

rain = [0, 12, 30, 0, 5, 0, 18]total = 0wet = []for r in rain:    total = total + r    if r > 10:        wet.append(r)print("Total:", total, "mm")print("Days over 10 mm:", wet)
Step 1 / 30

rain holds a week of rain in millimeters, one item a day.

Variables

rtotalwet
–––

Output

  
Answer

Python’s own helpers

Some questions are asked so often that Python answers them for you, on any list of numbers:

rain = [0, 12, 30, 0, 5, 0, 18]print(sum(rain), max(rain), min(rain), len(rain))print(sum(rain) / len(rain))print(sorted(rain))print(rain.count(0), "dry days")print("Wettest: day", rain.index(max(rain)) + 1)

Output

65 30 0 79.285714285714286[0, 0, 0, 5, 12, 18, 30]3 dry daysWettest: day 3

sum() is the running total, max() and min() the highest and lowest so far, written for you. rain.index(30) is a linear search (Grade 8): it gives the position of the first 30. Positions count from 0 but days from 1, hence the + 1. Use these helpers freely; write the loop yourself when you need something they don’t do, such as a filter, or a total of only some items.

What does max([12, 5, 30, 18]) - min([12, 5, 30, 18]) give?

Pick an answer to see why.

Strings: text, a character at a time

A string is a sequence too: its characters are in order, numbered from 0, exactly like a list’s items. So everything you know about positions works on text: len() counts the characters, city[0] is the first, city[-1] the last, and a for loop goes through it a character at a time. (In Unit 2 you used text[i] to find runs of letters.)

city = "Bhaktapur"print(city[0], city[4], city[-1])print(len(city))for ch in "Nepal":    print(ch)

Output

B t r9Nepal

A space, a comma and a digit are characters too, each with its own position.

Slices

Often you want a piece of a string, or of a list: the first four letters of a name, the last four digits of a phone number, the top three marks. A slice gives you that piece. city[2:5] is the characters from position 2 up to, but not including, position 5, just as range(2, 5) gives 2, 3 and 4.

The easiest way to think of a slice is to put the numbers on the cuts between the characters, not on the characters. city[2:5] cuts at 2 and at 5, and takes what is between: 5 − 2 = 3 characters.

Try it

Cut a slice

Move Start and Stop, and watch which characters are between the two cuts. How do you get the first three characters? The last four? What happens when the stop is before the start? Try the list too: slices work on lists the same way.

Bhaktapur
word[2:5] → 'akt' also word[-7:-4]

The slice is the characters between the two cuts: 3 of them, stop − start.

From the first cut to the last: a copy of the whole thing.

The stop is not after the start, so nothing is between them: the slice is empty, with no error.

What you should see
city = "Bhaktapur"print(city[2:5])print(city[:4])print(city[5:])print(city[-3:])print(city[::-1])

Output

aktBhakapurpurrupatkahB

The last one has a third number, a step, like range()’s: [::-1] goes through the whole string backwards, so it reverses it.

Slices of lists work the same way. marks[:3] is a new list of the first three marks, sorted(marks)[-3:] the three highest, and marks[:] a copy of the whole list (one more way to copy, besides .copy()).

Strings can’t be changed

A list’s items can be changed: marks[0] = 55. A string’s characters can’t. Try to correct a name’s first letter:

name = "sita"name[0] = "S"

Error message

Traceback (most recent call last):  File "main.py", line 2, in <module>    name[0] = "S"    ~~~~^^^TypeError: 'str' object does not support item assignment

A string is immutable: once made, it never changes. Instead you make a new string and put it in the variable: name = "S" + name[1:] joins "S" to everything from position 1 on, and gives "Sita". Every string method below works the same way: it never changes the string, it gives back a new one.

String methods

Strings have many methods. These are the ones you will use most:

Method What it gives back Example Gives
s.lower(), s.upper() all small, all capital letters "Nepal".upper() "NEPAL"
s.strip() without spaces at the start and end " tea ".strip() "tea"
s.strip(".,!") without those characters at the ends "Namaste!".strip(".,!") "Namaste"
s.replace(a, b) every a changed to b "984-123".replace("-", "") "984123"
s.count(a) how many times a comes in it "Kathmandu".count("a") 2
s.find(a) where a first starts, or −1 if it isn’t there "Kathmandu".find("man") 4
s.startswith(a), s.endswith(a) True or False "Kathmandu".startswith("Kath") True
s.isdigit() True if every character is a digit "2082".isdigit() True
s.center(n) the string in the middle of n characters, spaces on both sides "tea".center(7) " tea "
s.split(), s.split(",") a list of the pieces (next section) "a,b".split(",") ["a", "b"]

in works on strings too, as in Grade 8’s project, and finds a piece anywhere inside: "man" in "Kathmandu" is True.

code = "NP-977-2082". What is code[3:6]?

Pick an answer to see why.

word = "momo". What does word.upper() do to word?

Pick an answer to see why.

Words and lines: split and join

Text usually comes as one long string: a message, a paragraph, a line of a file. To work on its parts, cut it up. split() cuts a string at every space (any run of spaces, tabs or new lines) and gives back a list of the pieces:

sentence = "the tea is hot and the momo is hot too"words = sentence.split()print(words)print(len(words), "words")

Output

['the', 'tea', 'is', 'hot', 'and', 'the', 'momo', 'is', 'hot', 'too']10 words

Give it a character, and it cuts there instead. A line of a class record, with its values separated by commas, becomes a list of values:

line = "Sita,72,65,80"parts = line.split(",")print(parts)print(parts[0], int(parts[1]) + int(parts[2]) + int(parts[3]))

Output

['Sita', '72', '65', '80']Sita 217

The pieces are always strings, even when they look like numbers, so turn them into numbers with int() or float() before you calculate, as with input().

join() does the opposite: it glues a list of strings together, with the string before the dot between them.

places = ["Kathmandu", "Lalitpur", "Bhaktapur"]print(", ".join(places))print(" → ".join(places))

Output

Kathmandu, Lalitpur, BhaktapurKathmandu → Lalitpur → Bhaktapur

Cleaning words

Real text has capital letters and punctuation stuck to its words: "Namaste!" and "namaste" are different strings. Clean each word before you use it:

text = "Namaste! Namaste, everyone. Welcome to Nepal."words = []for w in text.split():    words.append(w.strip(".,!?").lower())print(words)

Output

['namaste', 'namaste', 'everyone', 'welcome', 'to', 'nepal']

Nepali text needs one more character stripped: the danda, ।, Nepali’s full stop. "हुँ।".strip("।") gives "हुँ". Nepali has no capital letters, so .lower() leaves it as it is.

Now a question: how many times does each word come in a paragraph? A list of words can’t answer it easily. You would need a second list of counts, and to search the first list every time to find where each word’s count is. There is a better shape for this job.

What does "10,20,30".split(",") give?

Pick an answer to see why.

Dictionaries: looking things up by name

Nobody finds a word in the Brihat Shabdakosh by its number (“the 23,481st word”). You look it up by the word itself, and find its meaning. Nobody calls a friend by remembering “the 47th contact”; you look up the name and find the number. A list finds a value by its position. A dictionary finds a value by its key: a name you choose for it.

A list: items by positionprices = [25, 60, 30]025160230prices[1] → 6060 is at position 1, but the listdoesn’t say what it is the price of.A dictionary: values by keymenu = {"tea": 25, "coffee": 60, …}"tea"25"coffee"60"samosa"30keysvaluesmenu["coffee"] → 60A list: items by positionprices = [25, 60, 30]025160230prices[1] → 6060 is at position 1, but the listdoesn’t say what it is the price of.A dictionary: values by keymenu = {"tea": 25, "coffee": 60, …}"tea"25"coffee"60"samosa"30keysvaluesmenu["coffee"] → 60
Figure 3.1 The same three prices kept two ways (made-up prices). A list gives the price at position 1, but says nothing about what it is the price of. A dictionary joins each key, an item’s name, to its value, its price.

A dictionary is written in curly brackets, { }, as key–value pairs: each key, a colon, its value, with commas between the pairs. Look a value up with its key in square brackets:

menu = {"tea": 25, "coffee": 60, "samosa": 30}print(menu["coffee"])menu["momo"] = 150print(menu)menu["tea"] = 30print(menu)print(len(menu))print("momo" in menu, "pizza" in menu)del menu["coffee"]print(menu)

Output

60{'tea': 25, 'coffee': 60, 'samosa': 30, 'momo': 150}{'tea': 30, 'coffee': 60, 'samosa': 30, 'momo': 150}4True False{'tea': 30, 'samosa': 30, 'momo': 150}

The rules:

  • menu["momo"] = 150 adds a key that isn’t there yet, and menu["tea"] = 30 changes the value of one that is. The same line does both: setting a key’s value makes the key if it is new.
  • Each key comes once. Two items can have the same value (tea and samosa both cost Rs 30), but not the same key. Write a key twice in the curly brackets, {"tea": 25, "tea": 30}, and the last value replaces the first: the dictionary is {'tea': 30}.
  • in asks whether a key is in the dictionary. "momo" in menu is True; 150 in menu is False, because 150 is a value, not a key.
  • len() counts the pairs, and del menu["coffee"] takes a pair out.
  • A dictionary keeps its pairs in the order they were added (Python has promised this since version 3.7, in 2018).
  • Keys are usually strings or numbers. Values can be anything: numbers, strings, lists, even other dictionaries.

A key that isn’t there

Looking up a key that isn’t in the dictionary stops the program:

menu = {"tea": 25, "coffee": 60, "samosa": 30}print(menu["pizza"])

Error message

Traceback (most recent call last):  File "main.py", line 2, in <module>    print(menu["pizza"])          ~~~~^^^^^^^^^KeyError: 'pizza'

A KeyError names the key it couldn’t find. When a key might be missing, either ask first with in, or use get(), which gives back a default you choose instead of stopping:

words = {"water": "पानी", "fire": "आगो", "sun": "सूर्य", "book": "पुस्तक"}word = input("English word: ")print(words.get(word.lower(), "not in my dictionary yet"))

Output

English word: Sunसूर्य

Run it again with a word that isn’t a key, such as moon. This is a tiny English–Nepali dictionary: a dictionary in both senses.

Going through a dictionary

A for loop over a dictionary goes through its keys. To get each key with its value, loop over .items(), which gives the pairs; .values() gives only the values.

menu = {"tea": 25, "coffee": 60, "samosa": 30}for item in menu:    print(item, menu[item])for item, price in menu.items():    print(f"{item}: Rs {price}")print("All three:", sum(menu.values()))

Output

tea 25coffee 60samosa 30tea: Rs 25coffee: Rs 60samosa: Rs 30All three: 115

for item, price in menu.items(): has two loop variables: each time round, the key goes into item and its value into price.

ages = {"Asha": 14, "Bimal": 15}. What does ages["Chhiring"] = 14 do?

Pick an answer to see why.

menu = {"tea": 25, "samosa": 30}. Which is True?

Pick an answer to see why.

Counting with a dictionary

Back to the question a list couldn’t answer easily: how many of each? Six students vote for class captain. A dictionary of counts answers it: the key is a name, the value its votes so far.

votes = ["Sita", "Bikash", "Sita", "Asha", "Sita", "Bikash"]counts = {}for name in votes:    if name in counts:        counts[name] = counts[name] + 1    else:        counts[name] = 1print(counts)

Output

{'Sita': 3, 'Bikash': 2, 'Asha': 1}

Try it

Count the votes

Step through it. When does a new key appear in counts, and when does a value go up?

votes = ["Sita", "Bikash", "Sita", "Asha", "Sita", "Bikash"]counts = {}for name in votes:    if name in counts:        counts[name] = counts[name] + 1    else:        counts[name] = 1print(counts)
Step 1 / 22

votes is a list of six votes for class captain, in the order they were counted.

Variables

namecounts
––

Output

 
Answer

This is the tally pattern: a dictionary used as a whole row of counters, one for each different thing, made as each new thing turns up. Lines 4 to 7 are so common that Python programmers usually write them as one line with get(): counts[name] = counts.get(name, 0) + 1 (the count so far, or 0 for a new name, plus 1).

To find the winner, use the highest-so-far pattern from Grade 8 on the pairs:

counts = {"Sita": 3, "Bikash": 2, "Asha": 1}winner = ""most = 0for name, n in counts.items():    if n > most:        winner = name        most = nprint(winner, "wins with", most, "votes")

Output

Sita wins with 3 votes

Why a dictionary is fast

To find a contact in a list of 1,000 names, Grade 8’s linear search could need 1,000 comparisons, or 10 with binary search on a sorted list. A dictionary of 1,000 names usually needs just one or two looks, and it needs no more for a million. It doesn’t search at all. From the key itself, it works out where the value is kept, and goes straight there. The way it works this out is called hashing, and Unit 4 counts the steps of all three ways.

Two wooden drawers of a library card catalog, numbered 808 and 809, the lower one labelled LOVER to LUMA
Figure 3.2 Two drawers of a library’s card catalog in the United States. Drawer 809 holds the cards for every heading from LOVER to LUMA. To find a book, a reader goes straight to one drawer instead of reading every card. Luhn’s buckets work the same way, except that a computer works out the drawer from the key itself. Photo: TBurmeister (WMF), CC BY-SA 4.0

Tables: rows and columns

A class register, a bus timetable, a table of Nepal’s provinces: much of the world’s data comes as a table, with a row for each thing and a column for each fact about it. In Python a table is most simply a list of rows, each row itself a list:

rows = [    ["Koshi", 14, 4961412],    ["Madhesh", 8, 6114600],    ["Bagmati", 13, 6116866],]print(rows[2])print(rows[2][0], rows[2][2])print(len(rows), "rows")

Output

['Bagmati', 13, 6116866]Bagmati 61168663 rows

rows[2] is one row, a list. rows[2][2] takes that row, then the item at position 2 in it: read it as “row 2, then position 2”. A list written over several lines, as here, is still one list: Python reads on until the closing bracket.

position in a row0120'Koshi'1449614121'Madhesh'861146002'Bagmati'1361168663'Gandaki'1124664274'Lumbini'1251220785'Karnali'1016884126'Sudurpashchim'92694783row numberrows[2]the whole row: a listrows[2][2]one value: row 2, position 2position in a row0120'Koshi'1449614121'Madhesh'861146002'Bagmati'1361168663'Gandaki'1124664274'Lumbini'1251220785'Karnali'1016884126'Sudurpashchim'92694783row numberrows[2]the whole row: a listrows[2][2]one value
Figure 3.3 Nepal’s seven provinces as a list of rows: each province’s name, its number of districts and its population in the 2021 census. The first number picks a row, the second a position in that row.

for row in rows: gives one whole row each time round. Adding up row[1] from every row is a total of a column; adding up the numbers along one row, a total of a row. To reach every value, put one loop inside another, a nested loop: the outer loop takes each row, the inner loop each position along it.

A photo is a table of numbers

A photo is a grid of pixels, each kept as a number (Unit 2). In a black-and-white photo with 8 bits a pixel, each pixel is a gray value from 0 (black) to 255 (white): the photo is a list of rows of gray values.

from nyatapola import photo takes the list photo from that file. show_grid(), from this page’s pixels module, draws a list of rows as a picture, a square for each value (on a computer, the Pillow library does this). This program draws the photo, then its negative, dark and light swapped:

from nyatapola import photofrom pixels import show_grid print(len(photo), "rows of", len(photo[0]), "pixels")print("Top left:", photo[0][0])print("Middle:", photo[15][20])show_grid(photo) negative = []for r in range(len(photo)):    row = []    for c in range(len(photo[0])):        row.append(255 - photo[r][c])    negative.append(row)show_grid(negative)

Output

30 rows of 40 pixelsTop left: 235Middle: 30

Picture

The Nyatapola temple in gray squares: its dark roofs against a pale sky.
The same temple as a negative: a dark sky behind pale roofs.

photo[r][c] is the pixel r rows down and c across, from 0 at the top left. The outer loop (line 10) goes down the rows, the inner loop (line 12) along each row, and 255 - photo[r][c] turns dark to light. Each new row is built with append(), then appended to negative: 30 rows of 40, 1,200 pixels.

Reading a table from a file

Nobody types a big table into a program. Tables are kept in files, and the simplest kind is a CSV file (comma-separated values): plain text, one row on each line, the values separated by commas, and usually the column names on the first line. A spreadsheet can save any sheet as a CSV file (Grade 7). Here is the whole table of provinces from the 2021 census, as a CSV file:

provinces.csv

province,districts,populationKoshi,14,4961412Madhesh,8,6114600Bagmati,13,6116866Gandaki,11,2466427Lumbini,12,5122078Karnali,10,1688412Sudurpashchim,9,2694783

A program on this page can read that file, as a program on a computer reads a file in its folder. open() opens it, .read() gives its whole text as one string, and .close() closes it. Then three steps you already know turn the text into a table: splitlines() cuts the text into its lines, split(",") cuts each line into its values, and int() turns the numbers from strings into numbers.

file = open("provinces.csv")text = file.read()file.close()lines = text.splitlines()print(lines[0])print(lines[3])rows = []for line in lines[1:]:    parts = line.split(",")    rows.append([parts[0], int(parts[1]), int(parts[2])])print(rows[2])print(len(rows), "provinces")

Output

province,districts,populationBagmati,13,6116866['Bagmati', 13, 6116866]7 provinces

Line 8 loops over lines[1:], a slice that leaves out line 0, the column names. Each line becomes a row of three values, two of them turned into ints, and is appended to rows.

Now any question about the provinces is a short loop. Here the reading is a function, so the questions can be asked below it:

def read_provinces(filename):    """Return the table as a list of rows: name, districts, population."""    file = open(filename)    lines = file.read().splitlines()    file.close()    rows = []    for line in lines[1:]:        parts = line.split(",")        rows.append([parts[0], int(parts[1]), int(parts[2])])    return rows rows = read_provinces("provinces.csv")total = 0biggest = rows[0]for row in rows:    total = total + row[2]    if row[2] > biggest[2]:        biggest = rowprint("Nepal:", total, "people")print("Most people:", biggest[0], biggest[2])for row in rows:    print(f"{row[0]}: {row[2] // row[1]} people a district")

Output

Nepal: 29164578 peopleMost people: Bagmati 6116866Koshi: 354386 people a districtMadhesh: 764325 people a districtBagmati: 470528 people a districtGandaki: 224220 people a districtLumbini: 426839 people a districtKarnali: 168841 people a districtSudurpashchim: 299420 people a district

The total, 29,164,578, is Nepal’s population in the 2021 census, which checks that every row was read. Bagmati has the most people, but only just: Madhesh has 2,266 fewer. And Madhesh’s districts have the most people each, more than four times Karnali’s, because it has only 8 districts on the crowded plains of the Tarai.

Writing a file

A program can also write a table to a file, for a spreadsheet or another program to read later. open() takes a second string, the mode, which says what you will do with the file:

Mode What it does
"r" read the file (the mode when you give none); an error if the file isn’t there
"w" write a new file; if the file is already there, everything in it is wiped first
"a" append: write at the end of the file, after what is already in it
"x" write a new file, but stop with an error if the file is already there

write() puts a string into the file. Unlike print(), it doesn’t start a new line by itself, so each line ends with "\n", the new-line character, and numbers must be turned into strings with str():

results = [["Asha", 72], ["Bimal", 58], ["Chhiring", 90]]file = open("results.csv", "w")file.write("name,marks\n")for name, marks in results:    file.write(name + "," + str(marks) + "\n")file.close() file = open("results.csv", "a")file.write("Dolma,64\n")file.close() file = open("results.csv", "r")print(file.read().strip())file.close()

Output

name,marksAsha,72Bimal,58Chhiring,90Dolma,64

Lines 2 to 6 write a new file, the column names and then one line per row. Lines 8 to 10 open it again with "a" and add a fifth line at the end. Lines 12 to 14 read it all back (strip() takes off the last new line). Run it twice: the file still has five lines, because "w" wipes it each time.

Many programs, and CDC’s books, open a file with with, which closes it by itself at the end of the block, even if the program stops with an error:

with open("results.csv", "r") as file:    text = file.read()

Tuples: rows that don’t change

Python has one more sequence, the tuple: values in round brackets, ("Bagmati", 13, 6116866). A tuple is like a list in every way you read it (positions, len(), slices, in, a for loop), but like a string it can’t be changed: no append(), no setting an item. Use a tuple for a group of values that belong together and shouldn’t change, such as a point’s x and y, or a date’s year, month and day.

row = ("Bagmati", 13, 6116866)row[1] = 14

Error message

Traceback (most recent call last):  File "main.py", line 2, in <module>    row[1] = 14    ~~~^^^TypeError: 'tuple' object does not support item assignment

You have used tuples already without knowing: for item, price in menu.items(): takes each pair as a tuple and unpacks it into two variables. Unpacking works on any sequence of the right length:

row = ("Bagmati", 13, 6116866)name, districts, people = rowprint(name, people)a = 5b = 9a, b = b, aprint(a, b)

Output

Bagmati 61168669 5

a, b = b, a is the swap promised in Grade 8: Python makes the tuple (9, 5) on the right first, then unpacks it into a and b. No temp is needed. A function can give back two values the same way: return low, high returns one tuple, which the caller can unpack.

def lowest_and_highest(marks):    return min(marks), max(marks) low, high = lowest_and_highest([52, 38, 71, 45])print(low, high)

Output

38 71

A list of dictionaries

In a list of rows you must remember that position 2 is the population. If a column is added in the middle one day, every row[2] in the program is suddenly wrong. Each row can be a dictionary instead, its keys the column names:

rows = [    {"province": "Koshi", "districts": 14, "population": 4961412},    {"province": "Karnali", "districts": 10, "population": 1688412},]for row in rows:    print(row["province"], row["population"])

Output

Koshi 4961412Karnali 1688412

row["population"] says what it is; row[2] doesn’t. A list of dictionaries is longer to type, but easier to read and harder to get wrong, and it is how most tables arrive from websites (Grade 10 calls this format JSON).

table = [["Asha", 72, 65], ["Bimal", 58, 80]]. What is table[1][2]?

Pick an answer to see why.

Which can’t be changed once it is made?

Pick an answer to see why.

Choosing a data structure

Lists, strings, tuples and dictionaries are Python’s main data structures: ways of holding many values so that a program can store them, find them and change them. Choosing the right one is often the biggest decision in a program. Ask these questions about your data:

Ask If yes Example
Is it text? a string; split() it when you need its words or values a message, a line of a file
Do you look things up by a name? a dictionary, the name as its key an item’s price; a word’s meaning; a district’s province
Do you count how many of each? a dictionary of counts (a tally) votes, words, items sold
Does the order matter, or do you go through all of it? a list marks to average; a queue; songs in a playlist; a day’s readings
Is it a few values that belong together and never change? a tuple a date, a point’s x and y
Is it a table? a list of rows: each row a list, or a dictionary with the column names as keys a class register; the provinces

Two warning signs that the structure is wrong: searching a list again and again for a name (use a dictionary), and keeping two lists side by side whose items must match up, names[i] with marks[i] (use one list of rows, or a dictionary from name to marks).

Try it

List or dictionary?

For each job, would you keep the data in a list or in a dictionary?

the marks of 40 students in a test, to find the average

Would you keep it in a list or a dictionary?

Tap a group above.

Card
1 of 10
Right the first time
0

Designing with data structures

To design a program, decide the data first, then the algorithm. Here is a design done step by step.

Reading code with lists and dictionaries

Programmers read more code than they write. When a function works with lists or dictionaries, read it by asking four questions: What does each parameter hold, and of what type? What does it return? Does it change the list or dictionary it was given? And why does it use this data structure?

The last two matter more than they seem. In Grade 8, bubble_sort(names) changed the list it was given, while sorted() gave back a new one. A function that takes a list can do either, and a program that calls it must know which.

def with_bonus(marks, bonus):    new = []    for m in marks:        new.append(m + bonus)    return new def add_bonus(marks, bonus):    for i in range(len(marks)):        marks[i] = marks[i] + bonus science = [52, 38, 71]better = with_bonus(science, 5)print(science, better)add_bonus(science, 5)print(science)

Output

[52, 38, 71] [57, 43, 76][57, 43, 76]

Try it

A function that returns, and one that changes

Step through it. Watch science in the top table while each function runs. Which function changes it?

def with_bonus(marks, bonus):    new = []    for m in marks:        new.append(m + bonus)    return new def add_bonus(marks, bonus):    for i in range(len(marks)):        marks[i] = marks[i] + bonus science = [52, 38, 71]better = with_bonus(science, 5)print(science, better)add_bonus(science, 5)print(science)
Step 1 / 23

def defines with_bonus, with two parameters: marks, a list, and bonus, a number.

Variables

sciencebetter
––

No function running

marksbonusminew
–––––

Output

  
Answer

Try it

Mistakes with lists, strings and dictionaries

Five short programs, each with one mistake. Read the message, tap the line, name the error, choose the fix.

What Python says

Traceback (most recent call last):
  File "main.py", line 3, in <module>
    print(fares[city])
          ~~~~~^^^^^^
KeyError: 'Chitwan'

1. Which line does the message name? Tap it in the program.

Read the message from the bottom up: the last line says what went wrong, the line above it where.

Program 1 of 5 · right the first time: 0 of 15

Project

In short

Exercise

fruits = ["mango", "litchi", "banana"]. What does fruits.append("orange") do?

Pick an answer to see why.

What is "Pokhara"[1:4]?

Pick an answer to see why.

Which line makes a dictionary?

Pick an answer to see why.

What does "a b c".split() give?

Pick an answer to see why.

stock = {"pens": 40, "copies": 25}. What does stock.get("rulers", 0) give?

Pick an answer to see why.

A program keeps a diary in diary.txt. Each day it must add that day’s line without losing the days before. How should it open the file?

Pick an answer to see why.

A program must remember each district’s headquarters, and find it from the district’s name. Which is best?

Pick an answer to see why.

a.

What is a data structure? Name four of Python’s, with an example of each written as Python, and say one job each suits.

Answer
b.

colors = ["blue", "white", "red", "green", "yellow"]. Give the value of each: i. colors[1] ii. colors[-1] iii. colors[1:3] iv. colors[:2] v. len(colors[2:]) vi. colors.index("red")

Answer
c.

Write the output of this program.

nums = [4, 7, 1]nums.append(9)nums.insert(1, 5)x = nums.pop()nums.remove(7)print(nums, x)
Answer
d.

What is the difference between a list and a dictionary? Explain with an example of the same data kept both ways, and say which you would choose for it, and why.

Answer
e.

Write a program that asks for a sentence and prints how many words it has, the longest word, and the sentence with its words in reverse order.

Answer
f.

Write a program that counts how many times each letter comes in a word the user types, using a dictionary. Show its output for banana.

Answer
g.

A student wants to give everyone’s marks a bonus of 5, but this program prints the old marks. Explain why, and correct it in two different ways.

marks = [52, 38, 71]for m in marks:    m = m + 5print(marks)
Answer
h.

Here is part of a CSV file of bus fares from Kathmandu (made-up fares). Write a program that reads lines like these from a string into a dictionary from town to fare, then prints the fare to Pokhara, and the cheapest town.

Fares

Pokhara,1200Chitwan,900Butwal,1100Dhulikhel,150
Answer
i.

Blur the Nyatapola photo: each new pixel is the average of the old one and its neighbors (the 3 × 3 square around it, fewer at the edges). Draw it. Why do the temple’s edges go soft?

Hint
Answer
j.

A shop wants a program to keep its stock: how many of each item it has, and to take an item off when one is sold, refusing a sale when none is left. Design it: choose the data structure and say why, write the algorithm for one sale in pseudocode, then write it as a Python function sell(stock, item) that returns True for a sale and False when it can’t sell.

Answer