7.1 — Iterables, Unpacking, Nested Lists

What’s wrong in this example?

1def average(nums):
2 total = 0
3
4 for x in grades:
5 total += x
6
7 return total / len(grades)
8
9
10grades = [85, 100, 98, 75]
11print(average(grades)) # 89.5

  • Try to avoid using global variables within functions when possible.
  • Assignment autograder will fail if you use global variable(s) instead of function parameter(s) inside a function.

Iterable

An iterable is a kind of object that can produce a sequence of other objects and hence can be used in a for loop.

  • range: sequence of integers
  • strings: sequence of characters
  • tuples: (immutable) sequence of any object
  • lists: sequence of any object
  • dictionaries: sequence of tuples (key, value)
  • sets: collection of immutable objects (but order is not defined)

Iterables can be used as arguments of the functions list(), set(), tuple(), dict(), etc.

1# range object only stores start, end and step size.
2print(range(10, 101, 10)) # range(10, 101, 10)
3
4# list stores all objects in memory
5l = list(range(10, 101, 10))
6print(l)
7# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
8
9s = set(range(10, 101, 10))
10print(s)
11# {100, 70, 40, 10, 80, 50, 20, 90, 60, 30}

“Performance of searching a list, a set and dictionary” from last week.

Sorting a list using sort method

1# list.sort() :
2# Sorts the list "in place" i.e. list will be modified.
3# Returns None.
4# The list is sorted in ascending order.
5
6grades = [90, 70, 60.5, 70, 80]
7grades.sort()
8print(grades) # [60.5, 70, 70, 80, 90]
9
10
11grades.sort(reverse=True) # descending order
12print(grades) # [90, 80, 70, 70, 60.5]

1grades = [90, 70, 60.5, 70, 80, "A"]
2grades.sort()
3# TypeError: '<' not supported between instances of 'str' and 'int'
4
5
6# Works with strings as well
7fruits = ["banana", "orange", "apple"]
8fruits.sort()
9print(fruits) # ['apple', 'banana', 'orange']

sorted function

1# sorted(iterable):
2# Return a new list containing all items from
3# the iterable in ascending order.
4# If any items cannnot be compared to each other, TypeError occurs.
5
6grades = [90, 70, 60.5, 70, 80]
7sorted_grades = sorted(grades)
8print(sorted_grades) # [60.5, 70, 70, 80, 90]
9print(grades) # [90, 70, 60.5, 70, 80]
10
11# Sort in descending order
12sorted_grades = sorted(grades, reverse=True)
13print(sorted_grades) # [90, 80, 70, 70, 60.5]

1# string is iterable
2word = "pineapple"
3sorted_letters = sorted(word)
4print(sorted_letters)
5# ['a', 'e', 'e', 'i', 'l', 'n', 'p', 'p', 'p']
6
7
8# set is iterable
9fruits = {"banana", "orange", "apple"}
10sorted_fruits = sorted(fruits)
11print(sorted_fruits) # ['apple', 'banana', 'orange']

1# dictionary is considered as an iterable of its keys
2inventory = {"sofa": 5, "table": 10, "chair": 20, "mattress": 5}
3sorted_names = sorted(inventory)
4print(sorted_names) # ['chair', 'mattress', 'sofa', 'table']
5
6# same as above
7sorted_names = sorted(inventory.keys())
8print(sorted_names) # ['chair', 'mattress', 'sofa', 'table']
9
10# iterable of values in the dictionary
11sorted_counts = sorted(inventory.values())
12print(sorted_counts) # [5, 5, 10, 20]

Packing vs Unpacking

When we create a string, a list, or a tuple, we are packing several elements into a single object.

1s = "cat"
2my_list = [5, 'a']
3my_tuple = (0, 3, 7)

Unpacking allows us to assign values in a string/list/tuple to multiple variables.
We must know the exact length of the string/list/tuple.

1s = "cat"
2a, b, c = s
3# a, b and c are all strings
4print(a) # c
5print(b) # a
6print(c) # t
7
8my_list = [5, 'cat']
9x, y = my_list
10print(x) # 5
11print(y) # cat

1# Parentheses are optional in this context.
2my_tuple = 0, 3, 7
3x, y, z = my_tuple
4print(x) # 0
5print(y) # 3
6print(z) # 7
7
8# Variables must match number of elements
9tup = 1, 2, 3
10x, y = tup
11# ValueError: too many values to unpack (expected 2)

Multiple assignment using packing/unpacking on same line

1# We are creating a tuple on the right side and
2# unpacking it into 3 variables.
3city, population, area = 'Montreal', 1704694, 431.5
4print(city) # Montreal
5print(population) # 1704694
6print(area) # 431.5

Returning a tuple from a function and unpacking

1def min_max(mylist):
2 # Return a tuple of two elements
3 return min(mylist), max(mylist)
4
5
6# Unpack the returned tuple into 2 variables
7x, y = min_max([2, -3, 10, 20])
8print(x) # -3
9print(y) # 20

Nested Lists

An element of a list can be another list!
Such lists are called nested lists.

1nested_list = [[1], [1, 2, 3], [1, 2]]
2print(type(nested_list))
3# <class 'list'>
4
5print(nested_list[0])
6# [1]
7print(nested_list[1])
8# [1, 2, 3]
9print(nested_list[2])
10# [1, 2]

Nested lists are useful to store data which come in form of a table or spreadsheet.

1# Name, A1, A2, A3
2student_grades = [["Student-A", 90, 95, 100],
3 ["Student-B", 85, 90, 98],
4 ["Student-C", 70, 75, 80]]

We can perform same operations on nested lists as we saw earlier: indexing, slicing, etc.

1student_grades = [["Student-A", 90, 95, 100],
2 ["Student-B", 85, 90, 98],
3 ["Student-C", 70, 75, 80]]
4
5# Print name of 2nd student
6print(student_grades[1][0])
7# Student-B
8
9# Change A2 grade for Student-B
10student_grades[1][2] = 100
11print(student_grades)
12# [['Student-A', 90, 95, 100], ['Student-B', 85, 100, 98],
13# ['Student-C', 70, 75, 80]]

Iterating in a row-first order

1matrix = [[81, 75, 90, 60],
2 [80, 70, 85, 55],
3 [40, 50, 45, 85]]
4
5num_rows = len(matrix)
6num_cols = len(matrix[0])
7
8print("Row-first order:")
9for r in range(num_rows):
10 for c in range(num_cols):
11 print(matrix[r][c], end=" ")
12 print()
Output
Row-first order:
81 75 90 60 
80 70 85 55 
40 50 45 85 

Iterating in a column-first order

1matrix = [[81, 75, 90, 60],
2 [80, 70, 85, 55],
3 [40, 50, 45, 85]]
4
5num_rows = len(matrix)
6num_cols = len(matrix[0])
7
8print("\nColumn-first order:")
9for c in range(num_cols):
10 for r in range(num_rows):
11 print(matrix[r][c], end=" ")
12 print()
Output
Column-first order:
81 80 40 
75 70 50 
90 85 45 
60 55 85

List of tuples

1points = [(1, 1, 3), (4, 10.5, 9), (7, 4.4, 9.7)]
2
3# List element can be modified:
4points[1] = (4, 12, 10) # Assign new point
5print(points) # [(1, 1, 3), (4, 12, 10), (7, 4.4, 9.7)]
6
7# Trying to change the second points's z-coordinate
8points[1][2] = 20
9# TypeError: 'tuple' object does not support item assignment

Iterating over list of tuples:

1points = [(1, 1, 3), (4, 10.5, 9),
2 (7, 4.4, 9.7)]
3
4for p in points: # p is a tuple
5 print(p)
6
7# Unpack a tuple into 3 variables
8for x, y, z in points:
9 print(x, y, z)
Output
(1, 1, 3)
(4, 10.5, 9)
(7, 4.4, 9.7)
1 1 3
4 10.5 9
7 4.4 9.7

Time for some problems on Ed Lessons.