Programming in Python · Lesson 8
Lists
One- and two-dimensional lists, indexing, traversal, list methods, list comprehension, filtering and mapping, and dictionaries.
Lesson purpose
So far each variable has held one value. This lesson introduces lists, Python’s main way to store a collection of values under one name, and dictionaries, which store values under meaningful keys instead of numeric positions. Students learn to create lists, access and change elements by index, traverse lists with loops, use built-in list functions and methods, build lists with list comprehension, work with two-dimensional lists (a tic-tac-toe board), and create, read, and modify dictionaries.
For students who know C# and JavaScript: a Python list behaves much like a JavaScript array or a C# List<T>; it grows and shrinks, and (like JavaScript) it can hold mixed types. A Python dictionary is like a JavaScript object used as a map, or a C# Dictionary<TKey, TValue>.
Learning objectives
By the end of the lesson, students should be able to:
- Categorize a list as a data structure and a composite data type, and distinguish one-dimensional from two-dimensional lists.
- List the three characteristics of a list and the types of data a list can hold.
- Explain list indexes, including negative indexes, and illustrate the layout of a one-dimensional list.
- Identify use cases for one- and two-dimensional lists.
- Declare and initialize empty and nonempty one-dimensional lists.
- Output a single element, explain index errors, traverse a list with a loop, and input list elements at runtime.
- Change an element, find an element, sum the elements, and modify every element in a list.
- Explain list comprehension and compare it to for-loops; explain filtering and mapping, and use them together.
- Initialize, store, traverse, and print two-dimensional lists, including a tic-tac-toe board.
- Create a dictionary, access and modify values by key, iterate over keys, add and update entries, and remove entries.
1. List basics
Why lists?
A tic-tac-toe board could be stored as nine separate variables (top_left, middle, and so on), but then nothing ties them together; you cannot do anything with the whole board at once. A better approach is a list.
A list is a data structure that stores a collection of elements in a specific order or location. Lists are classified as composite data types because they are built from primitive data types such as strings and integers.
Programmers typically work with:
- One-dimensional lists: linear, like a single row of the game board with its three elements.
- Two-dimensional lists: rows and columns forming a grid or matrix, like the entire board.
Lists can have more dimensions, but this course sticks to one and two.
Three characteristics of a list
- Heterogeneous. A list can contain any combination of data types (strings, integers, floats, Booleans). Lists usually hold one type per list, but Python does not require it.
- Ordered. Each element is identified by a sequential list index in square brackets (sometimes called a subscript). The first element’s index is 0.
- “Infinite.” A list can grow as large as your computer’s memory allows. Items can be appended to the end, middle, or beginning.
Clarifying note: the book says a list “needs a size when it is created” and is “initially fixed to the size you specify.” In Python, you never declare a list’s size in advance; you simply create it (even empty) and it grows or shrinks as you add or remove elements. This is different from
C#arrays, which do have a fixed length.
Indexing
For numbers = [6, 7, 8, 9, 10]:
| Element | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 |
The gotcha: a list of five elements has a last index of 4, not 5. The last index is always one less than the number of elements. (Same rule as string indexes in Lesson 5.)
Use cases
Instead of 100 separate variables for 100 rainfall measurements, use one list called rainfall. Lists are useful to:
- Store lists of data
- Process a collection of integers or floats with the same algorithm
- Sort numeric or string data
- Manipulate the characters in a word (reversing, encrypting)
- Process images stored as a matrix of pixels
- Implement data structures such as stacks, queues, and hash tables
- Store and manipulate mathematical matrices
- Search lists and collections of data
- Process lists or tables of data
2. Creating one-dimensional lists
A one-dimensional list has a name and uses a single index to identify elements. Name lists using the same conventions as variables (plural names, such as scores, help signal a collection).
The first row of the tic-tac-toe board as a list named first_row:
first_row[0] |
first_row[1] |
first_row[2] |
|---|---|---|
" " |
"O" |
"X" |
Check for understanding: What value is stored in index 2 of first_row? "X".
Syntax
empty_list = []
empty_list = list()
numbers_list = [2, 6, 2, 3, 9, 12]
letters_list = ["A", "H", "C", "B"]
mixed_list = [2, 4, "B", 2, "Z"]
- Square brackets
[]or thelist()function create an empty list. - To initialize with values, put the elements in square brackets separated by commas.
mixed_listshows that lists are heterogeneous.
3. One-dimensional list input and output
Output one element
numbers = [6, 7, 8, 9, 10]
print(numbers[3])
Output:
9
Indexes start at 0, so index 3 is the fourth element.
Negative indexes
Python also allows negative indexes: the last element is -1, the second to last is -2, and so on.
numbers = [6, 7, 8, 9, 10]
print(numbers[-3])
Output:
8
Index errors
Using an index that is too large (or a negative index that is too far back) produces an index error:
>>> numbers = [1, 2, 3, 4, 5]
>>> numbers[6]
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
numbers[6]
IndexError: list index out of range
numbers[-6] fails the same way. Index errors are also called boundary errors, out-of-bounds exceptions, or subscript errors. They are easy to make and easy to fix: check the index values in loops, calculations, and output statements.
Traversing a list
Printing every element one statement at a time is a brute-force approach:
print(numbers[0])
print(numbers[1])
print(numbers[2])
print(numbers[3])
print(numbers[4])
print(numbers) displays the whole list in square brackets, but a loop gives more control. Accessing each element in sequence is traversing a list. It is used to output elements, count them, search for an element, or sum them.
Check for understanding: What is the range of valid indexes for a list? From 0 to one less than the number of elements (and -1 back to the negative of the length).
len(a_list) returns the number of elements. There are two traversal styles:
Index-based traversal uses a loop counter so the code visits each valid index:
numbers = [3, 2, 9, 5, 1]
index = 0
while index < len(numbers):
print(index, numbers[index])
index += 1
print()
for i in range(len(numbers)):
print(i, numbers[i])
print()
Output (both loops):
0 3
1 2
2 9
3 5
4 1
Element-based traversal uses the list itself as the for-loop’s iterative sequence:
numbers = [3, 2, 9, 5, 1]
for element in numbers:
print(element)
Output:
3
2
9
5
1
Use element-based traversal when you only need the values; use index-based traversal when you need the position or must change elements. (C# connection: element-based is like foreach; index-based is like the classic for with a counter.)
Input list elements at runtime
Create an empty list, then use the append() method to add each new element to the end:
a_list.append(5)
The tricky part is knowing when to stop appending. Two options:
Known amount, specified in the program or by the user:
some_numbers = []
for i in range(5):
number = int(input("Enter number " + str(i) + ": "))
some_numbers.append(number)
print(some_numbers)
some_more_numbers = []
how_many = int(input("How many numbers do you want to enter? "))
for i in range(how_many):
number = int(input("Enter number " + str(i) + ": "))
some_more_numbers.append(number)
print(some_more_numbers)
Check for understanding: Where does the value for how_many come from? From the user’s input; it is then used to control the loop.
Sentinel value, a value that signals the user is done. Use this when the user may not know how many items there are:
some_numbers = []
print("Enter numbers or -1 to quit:")
number = int(input())
while number != -1:
some_numbers.append(number)
number = int(input())
print(some_numbers)
Sample run: entering 4, 8, 2, 4, 1, 7, then -1 prints [4, 8, 2, 4, 1, 7]. The sentinel itself is never appended. (This is the same priming-read while pattern from Lesson 7.)
4. List operations, functions, and methods
Change an element
numbers = [6, 7, 8, 9, 10]
numbers[3] = 22
print(numbers)
Output:
[6, 7, 8, 22, 10]
numbers[3] = 22 changes the fourth element from 9 to 22. Unlike strings (which are immutable), lists are mutable: their elements can be changed in place.
Modify every element
Element-based traversal cannot change values inside the list, because the loop variable is only a copy of each value. Modifying every item requires an index-based loop:
numbers = [6, 7, 8, 9, 10]
for i in range(len(numbers)):
numbers[i] += 1
print(numbers)
Output:
[7, 8, 9, 10, 11]
Find an element (count occurrences)
Loop through the list, compare each item to the target, and use an accumulator to count matches:
count = 0
numbers = [6, 22, 8, 22, 12, 6, 99, 20, 2, 4]
for element in numbers:
if element == 22:
count += 1
print("The array contains", count, "instances of 22.")
Output:
The array contains 2 instances of 22.
Check for understanding: Why can this code use an element-based loop? It only reads each value; it does not need the index or change the list.
The count() method does the same job in one line:
numbers = [6, 22, 8, 22, 12, 6, 99, 20, 2, 4]
total = numbers.count(22)
print("The array contains", total, "instances of 22.")
(A related check: 22 in numbers evaluates to True or False.)
Sum the elements
With an accumulator:
total = 0
numbers = [6, 7, 8, 9, 10]
for element in numbers:
total += element
print("The sum of the array:", total)
With the built-in sum() function:
numbers = [6, 7, 8, 9, 10]
total = sum(numbers)
print("The sum of the array:", total)
Both print The sum of the array: 40.
Common list functions and methods
| Function or method | Description | Example | Output |
|---|---|---|---|
min() |
Find the minimum value in a list | min([2,5,3,1]) |
1 |
max() |
Find the maximum value in a list | max([2,5,3,1]) |
5 |
sum() |
Add all elements together | sum([2,5,3,1]) |
11 |
count() |
Count elements that match the argument | [2,5,3,3,1].count(3) |
2 |
index() |
Index of the first element that matches the argument | [2,5,3,2,1].index(2) |
0 |
sort() |
Sort the list in place; the list must be a variable, and it is changed | numbers = [4,2,3,5,1] then numbers.sort() |
[1, 2, 3, 4, 5] |
Talking points:
min(),max(),sum(), andlen()are functions: the list goes inside the parentheses.count(),index(),sort(), andappend()are methods: they follow the list with a dot.sort()changes the list and returnsNone, sox = numbers.sort()storesNone. Usesorted(numbers)when you want a new sorted list and want to keep the original.- Understand the logic behind these tools (loops and accumulators), not just the shortcut.
5. List comprehension
Building a list with a loop vs. comprehension
To make a list of the numbers 0 through 99 with a loop:
numbers = []
for i in range(100):
numbers.append(i)
print(numbers)
Python lets you put the for-loop logic inside the square brackets, a technique called list comprehension:
numbers = [i for i in range(100)]
print(numbers)
Both produce [0, 1, 2, ..., 99]. Read it left to right: “the value i, for each i in range(100).”
Filtering
Filtering keeps only the elements that meet a condition. With a loop, that means an if statement inside the loop; with list comprehension, the if goes at the end:
evens = []
for i in range(100):
if i % 2 == 0:
evens.append(i)
print("Manual even numbers:")
print(evens)
print()
evens_LC = [i for i in range(100) if i % 2 == 0]
print("List comprehension even numbers:")
print(evens_LC)
Both produce [0, 2, 4, ..., 98].
Mapping
Mapping applies the same behavior to every element, using an expression in addition to the for-loop inside the brackets. Example: double each value.
doubled = []
for i in range(50):
doubled.append(i * 2)
print("Manual doubled numbers:")
print(doubled)
print()
doubled_LC = [i * 2 for i in range(50)]
print("List comprehension doubled numbers:")
print(doubled_LC)
Both produce [0, 2, 4, ..., 98].
Mapping and filtering together
Triple each number, but only for numbers divisible by 3:
result = []
for i in range(50):
if i % 3 == 0:
result.append(i * 3)
print("Manual tripling every third number:")
print(result)
print()
result_LC = [i * 3 for i in range(50) if i % 3 == 0]
print("List comprehension tripling every third number:")
print(result_LC)
Both produce [0, 9, 18, 27, 36, 45, 54, 63, 72, 81, 90, 99, 108, 117, 126, 135, 144].
Pattern to memorize: [expression for item in sequence if condition]
- The expression (left) is the mapping.
- The
if(right) is the filtering.
Check for understanding: What if the behavior you want to map is complicated? Use a regular for-loop (or, after Lesson 9, a function called inside the comprehension). Comprehensions are best for short, readable expressions.
C#/JavaScript connection: filtering and mapping are the same ideas as JavaScript’s .filter() and .map() or LINQ’s Where() and Select() in C#.
6. Two-dimensional lists
Rows and columns
A tic-tac-toe board is a two-dimensional list with rows and columns. Each cell is referenced by two indexes: board[row][column].
| Column 0 | Column 1 | Column 2 | |
|---|---|---|---|
| Row 0 | " " [0][0] |
"O" [0][1] |
"X" [0][2] |
| Row 1 | "O" [1][0] |
"O" [1][1] |
" " [1][2] |
| Row 2 | "X" [2][0] |
" " [2][1] |
"X" [2][2] |
Check for understanding: How would you refer to the cell in the middle column and bottom row? board[2][1].
Although it looks like a grid, a two-dimensional list is really a list that contains lists. The outer list holds three inner lists, one per row. Anything you can do with a one-dimensional list you can do with each row.
Initializing a two-dimensional list
Use nested square brackets. Put a comma after each inner list; the line breaks are optional but make the grid easy to see:
grid_of_numbers = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
Building a two-dimensional list from an empty list, with all elements set to 0:
matrix = []
rows = int(input("How many rows? "))
columns = int(input("How many columns? "))
for r in range(rows):
matrix.append([])
for c in range(columns):
matrix[r].append(0)
print(matrix)
Sample run:
How many rows? 3
How many columns? 4
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
Check for understanding: Why does the outer loop append an empty list, []? Each row must exist as its own inner list before the inner loop can append column values to it with matrix[r].append(0).
Caution for later:
[[0] * 4] * 3looks like a shortcut, but it creates three references to the same inner list, so changing one row changes all three. Build rows with a loop (as above) or with a comprehension:[[0] * columns for r in range(rows)].
Traversing a two-dimensional list
Traversing a two-dimensional list requires a nested loop: the outer loop moves through the rows, and the inner loop moves through the columns of the current row.
board = [[" ", "O", "X"],
["O", "O", " "],
["X", " ", "X"]]
for i in range(len(board)):
for j in range(len(board[i])):
print(board[i][j], end="")
print()
Output:
OX
OO
X X
- Row 0 visits
[0][0],[0][1],[0][2]; then row 1 visits[1][0],[1][1],[1][2]; then row 2. end=""keeps the inner-loop output on one line; theprint()after the inner loop ends each row.
Formatting output with join()
The join() method “glues” together a sequence of strings, using the string it is called on as the separator:
" ".join(letters)
If the list contains numbers, convert each element to a string first with list comprehension and mapping:
" ".join([str(x) for x in numbers])
Combining join() with a comprehension prints a two-dimensional board in one statement. Vertical bars make the empty spaces visible:
board = [[" ", "O", "X"],
["O", "O", " "],
["X", " ", "X"]]
print("\n".join(["|".join(row) for row in board]))
Output:
|O|X
O|O|
X| |X
The inner "|".join(row) builds each row’s text; the outer "\n".join(...) puts each row on its own line.
7. The tic-tac-toe game
Placing moves
A game is won when three spots in a row, column, or diagonal contain the same symbol. To make a move, a player chooses a row and column:
board = [[" ", " ", " "],
[" ", " ", " "],
[" ", " ", " "]]
print("\n".join(["|".join(row) for row in board]))
rowX = int(input("Player X row: "))
colX = int(input("Player X column: "))
board[rowX][colX] = "X"
print("\n".join(["|".join(row) for row in board]))
rowO = int(input("Player O row: "))
colO = int(input("Player O column: "))
board[rowO][colO] = "O"
print("\n".join(["|".join(row) for row in board]))
Check for understanding: Does this code play a full game? No. It places one X and one O. A full game needs a loop that alternates turns, validates moves, and checks for a winner.
Detecting a win on the top row
Use decomposition: solve the problem for a one-dimensional list first, then adapt it. For a single row variable, a win looks like row[0] == row[1] and row[1] == row[2]. For the board, replace row with board[0]:
board = [[" ", " ", " "],
["O", "O", "X"],
["X", "X", "O"]]
print("\n".join(["|".join(row) for row in board]))
if board[0][0] == board[0][1] and board[0][1] == board[0][2]:
print("There is a winning combination on the top row!")
else:
print("No winning combinations yet!")
Output ends with:
There is a winning combination on the top row!
Check for understanding: Why does it report a win when the top row is empty? Three spaces are equal to each other, too.
The fix checks that the first cell is not empty before comparing:
if board[0][0] != " " and board[0][0] == board[0][1] and board[0][1] == board[0][2]:
print("There is a winning combination on the top row!")
else:
print("No winning combinations yet!")
Output now ends with No winning combinations yet!.
Extension: have students write the checks for another row, a column (board[0][c], board[1][c], board[2][c]), and the two diagonals.
8. Dictionaries
Why dictionaries?
Suppose you track tic-tac-toe scores for friends in a list, with a separate index variable for each person:
mandy_index = 0
fernando_index = 1
jessica_index = 2
prateek_index = 3
scores = [2, 4, 1, 1]
print("Mandy's score:", scores[mandy_index])
print("Fernando's score:", scores[fernando_index])
print("Jessica's score:", scores[jessica_index])
print("Prateek's score:", scores[prateek_index])
This works but is tiresome to maintain. Every new friend needs a new variable, and if Mandy leaves, everyone after her shifts to a new index.
A dictionary is a data structure like a list, but instead of numeric indexes it uses (usually) strings to reference the data. Each entry has two parts:
- A key, used to look up the entry
- A value, the data stored for that key
Creating and reading a dictionary
Dictionaries use curly braces {}. Each entry is key: value, and entries are separated by commas:
scores = {"mandy": 2,
"fernando": 4,
"jessica": 1,
"prateek": 1}
print("Mandy's score:", scores["mandy"])
print("Fernando's score:", scores["fernando"])
print("Jessica's score:", scores["jessica"])
print("Prateek's score:", scores["prateek"])
Output:
Mandy's score: 2
Fernando's score: 4
Jessica's score: 1
Prateek's score: 1
- An empty dictionary is
{}. - Accessing a value looks like list indexing, but you put the key in the square brackets.
Missing keys
Using a key that is not in the dictionary causes an error:
print("Jessica's score:", scores["jessica"])
print("Mandy's score:", scores["mamdy"])
Jessica's score: 1
Traceback (most recent call last):
File "highScores.py", line 7, in <module>
print("Mandy's score:", scores["mamdy"])
KeyError: 'mamdy'
The misspelled key "mamdy" raises a KeyError. (Keys are also case-sensitive: "Mandy" and "mandy" are different keys.)
Iterating over a dictionary
print(scores) displays the whole dictionary in its curly-brace format. The keys() method returns the keys as an iterative sequence:
keys = scores.keys()
print("Keys:", ", ".join(keys))
Output: Keys: mandy, fernando, jessica, prateek
Loop over the keys to reach each value:
for key in scores.keys():
print("Name, score:", key + ",", scores[key])
Output:
Name, score: mandy, 2
Name, score: fernando, 4
Name, score: jessica, 1
Name, score: prateek, 1
(for key in scores: does the same thing, because looping over a dictionary visits its keys.)
Adding and updating entries
Assigning to a key adds the entry if the key does not exist, or updates it if it does. Only one value can be associated with each key.
name = input("Enter your name: ")
score = int(input("Enter your score: "))
scores[name] = score
for key in scores.keys():
print("Name, score:", key + ",", scores[key])
- Entering
georgeand0adds a new pair:george, 0. - Entering
jessicaand3updates Jessica’s value from 1 to 3.
Case-sensitivity note: the book’s sample run types
Jessica(capital J) and shows the existing"jessica"entry updated. Because keys are case-sensitive, typingJessicawould actually add a second key,"Jessica". Normalize input withname = input("Enter your name: ").lower()so updates match existing keys.
If two friends are both named Jessica, they need unique keys, such as "jessicaA" and "jessicaJ".
Removing entries
When Mandy leaves the tournament, use the pop() method with the key to remove:
scores.pop("mandy")
for key in scores.keys():
print("Name, score:", key + ",", scores[key])
Output:
Name, score: fernando, 4
Name, score: jessica, 1
Name, score: prateek, 1
Correction: the book says
pop()“does not return anything.” In fact,pop()returns the removed value, soold_score = scores.pop("mandy")stores 2. The dictionary is modified either way.
Classroom application
- Rainfall stats. Store a week of rainfall amounts in a list and print the total, average (
sum()/len()), minimum, and maximum. - Sentinel shopping list. Read item names until the user types
done, append each to a list, then print the list sorted. - Index vs. element loops. Give a list of prices; use an element-based loop to total them and an index-based loop to apply a 10% discount in place.
- Comprehension practice. Write comprehensions for: squares of 1 to 10; only the odd numbers from 0 to 30; each word in a list converted to uppercase.
- Tic-tac-toe checks. Extend the top-row win check to all rows, all columns, and both diagonals.
- Gradebook dictionary. Map student names to scores; let the user add or update a score and remove a student; print every name and score.
Common misconceptions
- “The last index equals the length.” The last index is
len(list) - 1; using the length causes anIndexError. - “Python lists have a fixed size.” Python lists grow and shrink;
append()adds andpop()removes. - “Changing the loop variable changes the list.” In
for element in numbers:, assigning toelementdoes not changenumbers; use an index-based loop. - “
numbers.sort()returns the sorted list.” It sorts in place and returnsNone; usesorted()for a new list. - “Lists are immutable like strings.” Lists are mutable; strings are not.
- “
board[1, 2]indexes a 2D list.” Python uses two sets of brackets:board[1][2]. - “
[[0] * 3] * 3makes a safe grid.” It repeats one inner list; build rows separately. - “Dictionary keys ignore case.”
"Jessica"and"jessica"are different keys. - “Reading a missing key returns nothing.” It raises a
KeyError. - “Assigning to an existing key adds a duplicate.” Keys are unique; assignment updates the existing value.
Lesson summary
- A list is a data structure that stores a collection of elements; it is a composite data type built from primitive types.
- Lists are heterogeneous, ordered, and can grow as needed. They have many use cases for collections of data.
- One-dimensional lists are linear; two-dimensional lists have rows and columns forming a grid or matrix.
- Each element is identified by an index in brackets; indexes can be positive (from the front, starting at 0) or negative (from the back, starting at -1).
- One-dimensional elements have one index; two-dimensional elements have a row index and a column index. Index errors are common but easy to find and fix.
- Traversing a list means accessing each element in sequence, with an index-based or element-based loop. A nested loop traverses a two-dimensional list.
- Common list operations include outputting, changing, searching for, inputting, and summing elements, using loops or functions and methods such as
len(),sum(),min(),max(),count(),index(),sort(), andappend(). - List comprehension builds lists inside square brackets; filtering keeps elements that meet a condition, and mapping applies an expression to each element.
- Dictionaries store key/value pairs in curly braces and use keys (usually strings) instead of numeric indexes.
keys()returns a dictionary’s keys for traversal; assignment adds a new pair or updates an existing key;pop()removes a pair.
Course Notes