Skip to content
CCC Python Course

Lists (1D and 2D)

Module
M1.8
Lesson
1 of 1
Reading time
4 min

In this lesson

  • Build and grow a list with a literal, .append(), and a list comprehension.
  • Summarize a list with sum(), sorted(), reversed(), any() and all(), and pair values with enumerate() and zip().
  • Build a 2D grid of lists, and avoid the [[0] * m] * n aliasing bug that shares one row across every entry.
  • Recognize which list operations cost more as a list grows, and allocate n + 1 slots for a 1-indexed problem.

Earlier modules stored one value at a time, or read one value at a time from a fixed-length line. Many CCC problems instead need to hold many values together, in the order they arrived, so a program can look back at any of them later. This lesson covers Python's list, and the built-in functions that summarize and rearrange one.

Building a list

A list literal holds several values together, written between square brackets and separated by commas, such as [10, 20, 30]. .append() grows a list one value at a time, adding a new value onto the end.

examples/build_with_append.py
n = int(input())scores = []for i in range(n):    scores.append(int(input()))print(scores)

Input

3
10
20
30

Output

[10, 20, 30]
Building a list by appending one value per pass of a loop

scores starts as an empty list, [], then each pass through the loop appends one more number read from input. After the loop, scores holds every number in the order it was read, ready to use as a whole.

A list comprehensionA one-line way to build a list from an existing sequence, written [expression for name in sequence], optionally filtered with if.In the glossary builds a list in one line instead, written [expression for name in sequence].

examples/list_comprehension.py
nums = [int(x) for x in input().split()]print(nums)evens = [x for x in nums if x % 2 == 0]print(evens)

Input

3 4 7 8 9

Output

[3, 4, 7, 8, 9]
[4, 8]
Building a list from a split line, then filtering it

[int(x) for x in input().split()] converts every piece from .split() into a number, all in one line, the same result build_with_append.py took a whole loop to reach. Adding if x % 2 == 0 at the end of a comprehension keeps only the values that pass that condition, building evens from just the even numbers in nums.

Summarizing a list

sum() adds up every value in a list. A generator expressionThe same shape as a list comprehension without square brackets, producing values one at a time instead of building a whole list.In the glossary has the same shape as a list comprehension, without the square brackets, and can be passed directly to a function like sum(), any() or all() without building a separate list first.

examples/sum_any_all_gen.py
nums = [int(x) for x in input().split()]print(sum(nums))print(sum(x for x in nums if x > 0))print(any(x < 0 for x in nums))print(all(x > 0 for x in nums))

Input

3 -2 5 -1

Output

5
8
True
False
Summing, filtering, and checking conditions across a list

sum(nums) adds every value. sum(x for x in nums if x > 0) adds only the positive ones, using a generator expression right inside the call. any(x < 0 for x in nums) is True if at least one value matches, and all(x > 0 for x in nums) is True only if every value does.

sorted() returns a new list with the same values in order, without changing the original. reversed() visits a list back to front, and list() turns what it produces back into an actual list. enumerate() pairs each value with its position.

examples/sorted_reversed_enumerate.py
nums = [int(x) for x in input().split()]print(sorted(nums))print(list(reversed(nums)))for i, x in enumerate(nums):    print(i, x)

Input

3 1 2

Output

[1, 2, 3]
[2, 1, 3]
0 3
1 1
2 2
Sorting, reversing, and pairing values with their positions

sorted(nums) gives [1, 2, 3] from [3, 1, 2], smallest first. list(reversed(nums)) gives [2, 1, 3], the original order backward. enumerate(nums) hands the loop both an index and a value on every pass, without a separate counter.

zip() pairs up values from two lists by position, stopping as soon as the shorter one runs out.

examples/zip_two_lists.py
names = input().split()scores = [int(x) for x in input().split()]for name, score in zip(names, scores):    print(name, score)

Input

Ana Bo
10 20

Output

Ana 10
Bo 20
Pairing two lists together by position

The first line splits into two names, the second into two scores, and zip(names, scores) pairs "Ana" with 10 and "Bo" with 20, matching each name to the score at the same position.

Building a 2D grid

A 2D grid is a list of lists: an outer list of rows, where each row is itself a list of columns. grid[r][c] reads the cell at row r, column c.

examples/grid_basics.py
rows = 2cols = 3grid = [[0] * cols for _ in range(rows)]grid[0][1] = 5print(grid)

Output

[[0, 5, 0], [0, 0, 0]]
Building a 2-row, 3-column grid, and changing one cell

[[0] * cols for _ in range(rows)] builds rows separate row lists, each with cols zeros. Changing grid[0][1] only changes that one cell, since every row is its own independent list.

A bug to avoid: sharing one row by accident

1grid = [[0] * 3] * 2
2grid[0][1] = 5
3print(grid)
Output so far
(nothing printed yet)
Frames and objects, step 1 of 4FramesObjectsGlobal frame
Speed
Python starts at the top of the file. Line 1 runs first.
  • Just changed

Figure 1Building a grid the wrong way: one row, repeated by reference

Read the steps as text

The program builds a 2-by-3 grid by repeating one row list twice, then changes one cell, which changes both rows because they are the same object.

  1. Python starts at the top of the file. Line 1 runs first.
  2. Line 1 runs: grid is created with the value [[0, 0, 0], [0, 0, 0]]. [0] * 3 builds one row. Repeating it with * 2 copies the reference to that same row twice, not the row itself.
  3. Line 2 runs; line 3 is next. grid[0][1] = 5 changes the one shared row object, so both grid[0] and grid[1] show the change.
  4. Line 3 runs: it prints [[0, 5, 0], [0, 5, 0]]. The program has finished: no lines are left to run.

[[0] * 3] * 2 looks like it builds two separate rows, but * 2 only copies the reference to the same row list twice, not the row itself. Changing grid[0][1] changes what both grid[0] and grid[1] point at, since they are the exact same list in memory.

examples/grid_fixed.py
grid = [[0] * 3 for _ in range(2)]grid[0][1] = 5print(grid)

Output

[[0, 5, 0], [0, 0, 0]]
The list-comprehension form builds an independent row each time

[[0] * 3 for _ in range(2)] runs [0] * 3 once per row, building a fresh list each time, so changing one row leaves the other alone. Whenever you build a 2D grid, use a comprehension with range() for the outer list, never * on a list that already holds a mutable row.

Slicing a list

A slice, written a_list[start:stop], pulls out a new list holding the elements from index start up to, but not including, index stop. Leaving start out means "from the beginning", and leaving stop out means "to the end", so a_list[:3] is the first three elements and a_list[2:] is everything from index 2 onward. a_list[:] with both sides left out copies the whole list.

A slice always builds a new list, even when it copies the whole thing; changing the slice afterward never touches the original. That makes slicing the safe way to hand off "a copy of these values" to another part of a program, in contrast to just writing other = a_list, which makes other a second name for the exact same list, so a change through either name shows up in both.

What lists cost as they grow

Some list operations get slower as a list grows longer: pop(0), insert(0, x), remove(), .index(), and checking x in a_list all have to shift or scan through the list's other elements. For most CCC input sizes this is not a concern, but a later module on list costs goes deeper into when it starts to matter.

Allocating slots for a 1-indexed problem

Some problems number their items starting from 1, not 0. Allocating n + 1 slots, instead of n, lets you use a problem's own numbering directly as an index.

examples/one_indexed_alloc.py
n = 5scores = [0] * (n + 1)scores[3] = 10print(scores)

Output

[0, 0, 0, 10, 0, 0]
Allocating one extra slot so index 1 through n line up with the problem's own numbering

[0] * (n + 1) gives indices 0 through n, one more than n items actually need. scores[3] stores team 3's score directly, at index 3, with no need to subtract 1 first or shift every other index to compensate.

Recap

This lesson covered building a list with a literal, .append(), and a list comprehension. It also covered summarizing a list with sum(), sorted(), reversed(), any() and all(), and pairing values with enumerate() and zip(). Last, it covered building a 2D grid safely, avoiding the shared-row bug, what a list's operations can cost as it grows, and allocating an extra slot for a 1-indexed problem. The three problems below all process several values held together in one list or grid.

Practice

Try these on the judges. Each link opens the problem on WMOJ or DMOJ.

  1. 2026 J2
    Olympic Scores (opens on WMOJ in a new tab) WMOJ

    Drop an athlete's highest and lowest judge scores, then scale what remains by a difficulty factor.

  2. 2023 J3
    Special Event (opens on WMOJ in a new tab) WMOJ

    Find which day of the week has the most people available from a list of weekly schedules.

  3. 2016 J2
    Magic Squares (opens on DMOJ in a new tab) DMOJ

    Check whether every row and column of a grid sums to the same value.

    Why DMOJ: Tries the same kind of problem on DMOJ, which holds 2014 to 2020.