# A list of integers
= [1, 2, 3, 4, 5]
integer_list
# A list of mixed data types
= [42, "apple", 3.14, True]
mixed_list
# A list of lists
= [integer_list, mixed_list] lists_list
10 Data Structures: Lists and Tuples
In the previous chapters, we introduced the basic elements of programming in Python, including variables, data types, control structures, and functions. Now, we turn our attention to an essential topic in computing — data structures. Data structures allow us to organize and store data in ways that enable efficient access and modification. In this chapter, we will explore two foundational data structures in Python: Lists and Tuples.
10.1 Lists
In Python, lists are one of the most commonly used data structures due to their flexibility and ease of use. A list is a mutable, ordered collection of elements, which means the elements in a list can be changed after the list is created, and they are stored in a specific order. This makes lists ideal for storing sequences of data that might need to be altered during the execution of a program.
10.1.1 Creating Lists
Lists in Python are defined using square brackets ([]
), with each element separated by a comma. A list can contain elements of any data type, including integers, floats, strings, Booleans, and even other lists.
Examples:
The flexibility of lists allows you to store a wide variety of data in a single collection, making them useful in many programming contexts, such as storing records, handling input, and building dynamic datasets.
10.1.2 Accessing Elements in a List
Each element in a list is associated with an index—an integer representing the element’s position in the list. In Python, indices start at 0, meaning the first element is accessed with index 0
, the second element with index 1
, and so on. Negative indices can also be used to access elements from the end of the list, with -1
referring to the last element, -2
to the second-to-last, and so on.
Examples:
# Accessing elements by positive index
= [10, 20, 30, 40]
my_list print(my_list[0]) # Output: 10
print(my_list[2]) # Output: 30
# Accessing elements by negative index
print(my_list[-1]) # Output: 40
print(my_list[-2]) # Output: 30
10
30
40
30
10.1.3 Modifying Lists
One of the most powerful features of lists is their mutability. This means that after a list is created, its elements can be changed, added, or removed without creating a new list. There are several ways to modify lists in Python.
Changing Elements
You can change individual elements in a list by assigning a new value to a specific index:
= [1, 2, 3]
my_list 1] = 99
my_list[print(my_list)
[1, 99, 3]
Adding Elements
You can add elements to a list using the append()
method (to add a single element) or the extend()
method (to add multiple elements):
# Adding a single element
= [1, 2, 3]
my_list 4)
my_list.append(print(my_list)
# Adding multiple elements
5, 6])
my_list.extend([print(my_list)
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6]
You can also use the insert()
method to add an element at a specific index:
= [1, 2, 3]
my_list 1, "new")
my_list.insert(print(my_list)
[1, 'new', 2, 3]
In Python, a method is a function that is associated with an object. Every method is a function, but not every function is a method. The key difference is that methods are called on objects, and they are designed to perform actions related to those objects. The syntax for calling a method involves the dot notation, where you first specify the object and then call the method.
Methods are tightly bound to the object they belong to and often manipulate or interact with the data stored in the object. Python has built-in methods for its standard data types like lists, strings, dictionaries, etc.
Removing Elements
Elements can be removed from a list using the remove()
method (to remove the first occurrence of a specific element) or the pop()
method (to remove an element by its index):
# Removing an element by value
= [1, 2, 3, 2]
my_list 2)
my_list.remove(print(my_list)
# Removing an element by index
1) #note that this returns the value being removed
my_list.pop(print(my_list)
[1, 3, 2]
[1, 2]
To clear all elements from a list, use the clear()
method:
= [1, 2, 3]
my_list
my_list.clear()print(my_list)
[]
10.1.4 Slicing Lists
In addition to accessing individual elements, Python allows you to slice lists, which means extracting a portion of the list to create a new list. Slicing is done using the colon (:
) operator, with the format list[start:end]
. The start
index is inclusive, while the end
index is exclusive.
Examples:
= [10, 20, 30, 40, 50]
my_list
# Extract elements from index 1 to 3 (2nd to 4th elements)
print(my_list[1:4]) # Output: [20, 30, 40]
# Extract elements from the start to index 3
print(my_list[:4]) # Output: [10, 20, 30, 40]
# Extract elements from index 2 to the end
print(my_list[2:]) # Output: [30, 40, 50]
[20, 30, 40]
[10, 20, 30, 40]
[30, 40, 50]
Slicing can also be used with a step value, which specifies how many elements to skip between items:
# Extract every other element
print(my_list[::2])
[10, 30, 50]
10.1.5 Common List Operations
Python provides several built-in functions and operators that can be applied to lists. Below are some of the most commonly used list operations:
Checking Length: You can find the number of elements in a list using the
len()
function:= [10, 20, 30] my_list print(len(my_list))
3
Membership Testing: You can check whether an element is in a list using the
in
keyword:= [10, 20, 30] my_list print(20 in my_list)
True
Sorting Lists: Lists can be sorted in place using the
sort()
method, or a sorted copy of the list can be returned using thesorted()
function:= [3, 1, 4, 1, 5, 9] my_list # Sort the list in place my_list.sort() print(my_list)
[1, 1, 3, 4, 5, 9]
Reversing a List: You can reverse the order of elements in a list using the
reverse()
method:= [1, 2, 3] my_list my_list.reverse()print(my_list)
[3, 2, 1]
10.1.6 Iterating Over Lists
Lists are iterable, which means you can loop through the elements using a for
loop:
= ["apple", "banana", "cherry"]
my_list for fruit in my_list:
print(fruit)
apple
banana
cherry
You can also iterate over both the index and the element using the enumerate()
function:
= ["apple", "banana", "cherry"]
my_list for index, fruit in enumerate(my_list):
print(f"Index {index}: {fruit}")
Nesting Lists
Lists can contain other lists as elements, allowing you to create more complex data structures like matrices or grids. This is known as nesting.
Example:
= [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix
# Accessing the second element of the first list
print(matrix[0][1]) # Output: 2
# Iterating over nested lists
for row in matrix:
print(row)
2
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
Lists and Built-in Libraries
Lists can be used effectively with common Python libraries. For example, the random
module allows for random selection of elements from a list:
Example:
import random
= ["apple", "banana", "cherry", "date"]
fruits print(random.choice(fruits)) # Randomly selects and prints one fruit
date
10.2 Tuples
While lists offer flexibility through their mutability, Python also provides tuples, which are similar in structure but immutable. Once a tuple is created, its elements cannot be changed. This immutability makes tuples useful for representing fixed collections of items that should not or cannot change throughout the execution of a program. Tuples are ideal when you need to ensure data integrity, such as coordinates, configuration settings, or when passing multiple values from functions where immutability is expected.
10.2.1 Creating Tuples
Tuples are defined using parentheses ()
and can hold elements of any data type, much like lists. However, since they are immutable, you cannot modify the elements of a tuple after it is created.
Syntax:
# Creating a tuple
= (10, 20, 30) my_tuple
It’s also possible to create tuples without using parentheses, though this is less common:
= 10, 20, 30 my_tuple
Tuples can contain elements of different types:
= (1, "apple", 3.14, True) mixed_tuple
Tuples with a single element need to have a comma after the element to avoid confusion with parentheses used in expressions:
= (5,)
single_element_tuple print(type(single_element_tuple))
<class 'tuple'>
10.2.2 Accessing Tuple Elements
Like lists, tuples are indexed starting at 0, and individual elements can be accessed using their index. However, since tuples are immutable, you cannot modify their elements.
Examples:
= (10, 20, 30, 40)
my_tuple
# Accessing the first element
print(my_tuple[0])
# Accessing the last element using negative indexing
print(my_tuple[-1])
10
40
You can slice tuples just like lists, returning a new tuple containing the specified range of elements:
# Slicing a tuple
print(my_tuple[1:3])
(20, 30)
10.2.3 Tuple Operations
Although tuples are immutable, there are still several operations you can perform on them:
Length of a Tuple: You can find the number of elements in a tuple using the
len()
function:= (10, 20, 30) my_tuple print(len(my_tuple))
3
Membership Testing: Use the
in
keyword to check whether an element exists in a tuple:= (10, 20, 30) my_tuple print(20 in my_tuple)
True
Iterating Over a Tuple: Tuples are iterable, so you can loop through their elements using a
for
loop:for item in my_tuple: print(item)
10 20 30
Indexing and Slicing: Like lists, tuples support indexing and slicing:
= (10, 20, 30, 40) my_tuple print(my_tuple[1:3])
(20, 30)
10.2.4 Nested Tuples
Tuples can be nested inside other tuples, allowing you to create multi-level data structures. This is useful when organizing complex data, such as in multidimensional arrays or coordinate systems.
Example:
= ((1, 2), (3, 4), (5, 6))
nested_tuple
# Accessing the first tuple
print(nested_tuple[0])
# Accessing an element from a nested tuple
print(nested_tuple[0][1])
(1, 2)
2
10.2.5 Unpacking Tuples
One of the most powerful features of tuples is tuple unpacking, which allows you to assign the elements of a tuple to individual variables in a single statement.
Example:
# Unpacking a tuple into variables
= (10, 20)
coordinates = coordinates
x, y print(x)
print(y)
10
20
Tuple unpacking is especially useful when functions return multiple values in a tuple, allowing you to capture and work with these values directly.
Example:
def rectangle_properties(length, width):
= length * width
area = 2 * (length + width)
perimeter return (area, perimeter)
# Unpacking the returned tuple into variables
= rectangle_properties(5, 10)
area, perimeter print(f"Area: {area}, Perimeter: {perimeter}")
Area: 50, Perimeter: 30
10.2.6 Tuple Methods
Since tuples are immutable, they have fewer methods compared to lists. However, they do provide two useful methods:
count(): Returns the number of times a specified value appears in the tuple.
= (1, 2, 2, 3, 2) my_tuple print(my_tuple.count(2))
3
index(): Returns the index of the first occurrence of a specified value.
= (1, 2, 3) my_tuple print(my_tuple.index(2))
1
10.3 List Comprehension
In Python, list comprehension provides a concise way to generate lists. It offers a more readable and often more efficient alternative to using loops and append()
to build lists. List comprehension allows you to apply an expression to each element in a sequence and optionally include conditional statements to filter elements. This compact syntax makes it easy to create lists based on existing sequences or from operations.
10.3.1 Basic Syntax of List Comprehension
The basic syntax of list comprehension is:
for item in iterable] [expression
expression
: This is the operation or value you want to apply to each item in the iterable.item
: Each element from the iterable (e.g., a list, tuple, or string).iterable
: The sequence of elements to loop over (e.g., a list, range, or other iterable object).
This simple form of list comprehension generates a new list by evaluating the expression for each element in the iterable.
Example: Creating a list of squares
= [x**2 for x in range(5)]
squares print(squares)
[0, 1, 4, 9, 16]
In this example, for each value x
in range(5)
, Python evaluates x**2
and adds the result to the new list. The result is a list of the squares of the numbers from 0 to 4.
10.3.2 List Comprehension with Conditional Logic
List comprehension can include conditional logic, allowing you to filter elements or apply an operation only when a condition is met. The syntax for adding a condition is as follows:
for item in iterable if condition] [expression
The condition
is a logical statement that is evaluated for each item in the iterable. Only items for which the condition evaluates to True
are included in the resulting list.
Example: Filtering even numbers
= [x for x in range(10) if x % 2 == 0]
even_numbers print(even_numbers)
[0, 2, 4, 6, 8]
Here, only numbers that satisfy the condition x % 2 == 0
(i.e., the even numbers) are included in the resulting list.
You can also use an if-else
expression within list comprehension for more complex logic:
if condition else expression_if_false for item in iterable] [expression_if_true
Example: Labeling even and odd numbers
= ["even" if x % 2 == 0 else "odd" for x in range(5)]
labels print(labels)
['even', 'odd', 'even', 'odd', 'even']
In this case, the comprehension adds the string "even"
to the list if x
is divisible by 2 and "odd"
otherwise.
10.3.3 Nested List Comprehension
List comprehension can also be nested to create lists from multidimensional structures, such as matrices. Nested list comprehensions are powerful but can become harder to read if not used carefully.
The basic syntax for nested list comprehension is:
for item1 in iterable1 for item2 in iterable2] [expression
Example: Flattening a matrix (a list of lists)
= [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix = [num for row in matrix for num in row]
flat_list print(flat_list)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
In this example, we loop over each row in the matrix, and for each row, we loop over each number, adding it to a single list (flat_list
).
Example: Multiplying elements in a matrix by 2
= [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix = [[num * 2 for num in row] for row in matrix]
doubled_matrix print(doubled_matrix)
[[2, 4, 6], [8, 10, 12], [14, 16, 18]]
In this case, the list comprehension multiplies each element in the matrix by 2, resulting in a new matrix where all values are doubled.
10.3.4 List Comprehension with Built-in Functions
You can combine list comprehensions with built-in Python functions to perform more complex transformations and calculations. This is a common pattern when dealing with operations such as string manipulation, mathematical calculations, or other functional transformations.
Example: Using the len()
function
= ["apple", "banana", "cherry"]
words = [len(word) for word in words]
word_lengths print(word_lengths)
[5, 6, 6]
Here, the list comprehension applies the len()
function to each word in the list words
, resulting in a list of the word lengths.
Example: Using sum()
with list comprehension Suppose we have a list of lists representing test scores for different students. We can calculate the sum of each student’s test scores using list comprehension:
= [[75, 80, 85], [60, 70, 75], [90, 95, 100]]
scores = [sum(student_scores) for student_scores in scores]
total_scores print(total_scores)
[240, 205, 285]
In this example, sum()
calculates the total score for each student, and list comprehension gathers these sums into a new list total_scores
.
10.3.5 List Comprehension vs. Loops
List comprehension is often favored over traditional loops because it provides a more compact and readable syntax. However, there are cases where a loop might be more appropriate, especially when the logic is complex, or when you need to modify elements in place.
Example: Traditional loop
= []
squares for x in range(5):
**2)
squares.append(xprint(squares)
[0, 1, 4, 9, 16]
Equivalent list comprehension:
= [x**2 for x in range(5)]
squares print(squares)
[0, 1, 4, 9, 16]
List comprehension is faster for simple operations because it avoids the overhead of repeatedly calling append()
and performing function calls. However, for more complex logic, such as multiple conditional statements or nested loops, the clarity of traditional loops might outweigh the brevity of list comprehension.
10.3.6 List Comprehension with External Libraries
List comprehension can be used effectively with other basic libraries like math
or random
.
Example: Using math.sqrt()
with list comprehension
import math
= [1, 4, 9, 16, 25]
numbers = [math.sqrt(num) for num in numbers]
square_roots print(square_roots)
[1.0, 2.0, 3.0, 4.0, 5.0]
In this example, math.sqrt()
is applied to each number in the list numbers
, resulting in a list of square roots.
Example: Using random.randint()
You can also use list comprehension with the random
module to generate random numbers:
import random
= [random.randint(1, 100) for _ in range(5)]
random_numbers print(random_numbers)
[13, 63, 93, 71, 13]
Here, the _
is a placeholder variable (indicating that the value is not important), and random.randint()
generates a random integer for each iteration.
10.3.7 Limitations of List Comprehension
While list comprehension is a powerful tool, there are some cases where it may not be the best choice:
Readability: Overusing or nesting list comprehensions can make code difficult to read and maintain, especially when working with complex logic. In such cases, using traditional loops or helper functions might result in clearer and more maintainable code.
Complex operations: When performing complex operations with multiple steps, it’s often better to use traditional loops to avoid confusion and improve code clarity.
Memory efficiency: Since list comprehensions create a new list in memory, they might not be suitable for extremely large datasets. In such cases, consider using generator expressions (which we will cover in later sections) to optimize memory usage.
10.3.8 Example: Practical Applications of List Comprehension
To conclude, let’s explore a practical application of list comprehension in data processing.
Example: Filtering and transforming data Suppose we are processing a list of student scores, and we want to filter out scores below 50 and increase the remaining scores by 10%. We can accomplish this efficiently with list comprehension.
= [45, 67, 85, 30, 78, 92, 40]
scores = [score * 1.1 for score in scores if score >= 50]
adjusted_scores print(adjusted_scores)
[73.7, 93.50000000000001, 85.80000000000001, 101.2]
In this example, only scores greater than or equal to 50 are included, and each selected score is increased by 10%. List comprehension simplifies the process of filtering and transforming the data in a single readable line.
10.4 Exercises
Excersice 1: Basic List Operations
- Create a list named
fruits
with the values: “apple”, “banana”, “cherry”.
- Add the fruit “orange” to the list.
- Remove “banana” from the list.
- Print the second fruit in the list.
- Print the length of the list.
Excersice 2: Modifying Lists
- Create a list
numbers
containing the values 1, 2, 3, 4, and 5.
- Replace the third element in the list with the value 10.
- Add the number 6 to the end of the list.
- Insert the number 0 at the beginning of the list.
- Print the updated list.
Excersice 3: Slicing Lists
Given the list colors = ["red", "green", "blue", "yellow", "purple"]
,
a. Slice and print the first three colors.
b. Slice and print the last two colors.
c. Slice and print every second color in the list.
Excersice 4: Nested Lists
Create a nested list matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
.
a. Print the element at the first row and second column.
b. Change the element at the second row and third column to 10.
c. Print the entire second row.
Excersice 5: Basic Tuple Operations
- Create a tuple
my_tuple
with values: 10, 20, 30, 40, 50.
- Print the value at index 2.
- Try to change the value at index 1 to 15 (what happens?).
- Print the length of the tuple.
Excersice 6: Tuple Unpacking
- Create a tuple
dimensions = (1920, 1080)
.
- Unpack the tuple into variables
width
andheight
.
- Print the values of
width
andheight
.
Excersice 7: Returning Tuples from Functions
- Write a function
calculate_stats(numbers)
that takes a list of numbers and returns a tuple containing the sum and the average of the list.
- Call the function with the list
[10, 20, 30, 40, 50]
and unpack the returned tuple into variablestotal_sum
andaverage
.
- Print the values of
total_sum
andaverage
.
Excersice 8: Nested Tuples
Given the nested tuple nested = ((1, 2), (3, 4), (5, 6))
,
a. Print the first element of the second tuple.
b. Try to change the second element of the third tuple to 7 (what happens?).
Excersice 9: Basic List Comprehension
- Create a list comprehension that generates a list of squares of numbers from 1 to 10.
- Create a list comprehension that generates a list of even numbers between 1 and 20.
Excersice 10: Basic List Comprehension
- Create a list comprehension that generates a list of all numbers between 1 and 50 that are divisible by 3.
- Create a list comprehension that generates a list of all numbers between 1 and 100 that are divisible by both 2 and 5.
Excersice 11: Nested List Comprehension
Using nested list comprehension, create a list of all possible combinations of two numbers, where the first number is from the list [1, 2, 3]
and the second number is from the list [4, 5, 6]
.
Excersice 12: String Manipulation with List Comprehension
Given the list words = ["apple", "banana", "cherry", "date"]
,
a. Create a list comprehension that returns the lengths of each word in the list.
b. Create a list comprehension that converts each word in the list to uppercase.
Excersice 13: Tuples and List Comprehension
Given a list of tuples representing students and their scores:
students = [("Alice", 85), ("Bob", 60), ("Charlie", 95), ("David", 70)]
,
a. Use a list comprehension to create a list of names of students who scored 70 or above.
b. Use a list comprehension to create a list of tuples where each student’s score is increased by 5 points.
Excersice 14: Matrix Flattening
Given a nested list (matrix): matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
, use list comprehension to flatten the matrix into a single list.