Python for AI 103
Acknowledgement: This post is based mainly on Dr. Quang-Vinh Dinh’s presentation and slides and partly on on TA, STA, and peers’ responses from the course, AIO2025, offered by AI VIETNAM.
Relevance: The audience of this lecture were those who have read Python for AI 102, had some coding experience, and known a bit of AI knowledge. Absolute beginners are encouraged to read the material and repeatedly practice coding without AI assistance to strengthen their coding and code-reading skills. Note that only necessary Python knowledge is introduced here.
Think about anything you learn as a game. A game always has rules. We just need to follow the rules to solves problems and conquer the game. When learning programming, don’t trust anyone or any book. Only be convinced that a program is correct once you have already tried it yourself, seen the expected result, and explore the reasoning behind the answer through questioning.
-Dr. Quang-Vinh Dinh
In today’s post, we’ll delve into lists. We won’t approach it like computer scientists who know how to integrate it into embedded systems and make physical devices perform specific tasks. Instead, we’ll take a programming-focused approach.
Pro tip: When learning a new programming concept, it’s best practice to read the documentation and experiment with different examples to gain a deeper understanding.
Data Structure
Data structures were orginally invented to help computers handle data faster. Realistically, speed has become less of a concern due to regular hardware upgrades.
A data struture is a format used to organize and store data for efficient access and management. It often mirrors real-life situations, for instance, organizing words in a dictionary, managing a customer queue, or building a family tree.
There are two types of data structures and they are arranged in order of their usage frequency from most to least commonly used. Lists can be used to custom user-defined data structures.
| Built-in | User-defined |
|---|---|
| List | Stack |
| Dictionary | Queue |
| Tuple | Tree |
| Set | Graph |
| Linked List |
1D List
list_name = [element_1, ..., element_n]
A list is a container of elements. As mentioned in the previous post, a list can contain different types of data, but we stick with only one data type per list in practice—especially numbers—since computers internally works with numerical data.
list_0 = [] # an empty list
list_00 = list() # an empty list
list_1 = [7, -5, 8]
list_2 = [5, 4.7, -2, "hello", 0, True, -1000.1, 5, 5, 5]
list_3 = ["aivietnam", [2, -3, 0, 5]]
Some basic rules for lists are:
- All elements are surrounded by a pair of brackets.
- Every two consecutive elements are separated by a comma.
- As soon as a list is created, so are its indices.
-
Forward index: starts at $0$ and increases by $1$. -
Backward index: starts at $-1$ and decreases by $1$. -
list_name[index]accesses an element andindexmust be less than the number of elements in a list.
-
We mainly use forward index, and sometimes use $-1$ from backward index to retrieve the last element.
list_1 = [7, -5, 8]
# test
list_1[0]
>>> 7
list_1[-1]
>>> 8
list_1[9]
>>> IndexError
Operators
We can manipulate lists by performing various operations on them.
Updating an Element
Lists are mutable, meaning their content can be changed after creation. Mutability and immutability are coming soon in this post.
list_1 = [7, -5, 8]
list_0 = []
# test
list_1[2] = 10000
list_1
>>> [7, -5, 10000]
list_0[1] = 5
list_0
>>> IndexError
Slicing
Similar to the range(start, stop, step) function, the slicing syntax of list is list[start:end:step].
- start, end, and step are indices. Their values are all optional.
- start is $0$ and step is $1$ by default.
- If end is not specified, slicing takes all elements till the last element of one side depending on step.
Slicing creates an entirely new list—changing its elements’ value does not affect the original list and vice versa.
a_list = [3, 4, 5, 6, 7, 8, 9, 10]
# test
a_list[:3]
>>> [3, 4, 5]
a_list[6:]
>>> [9, 10]
a_list[0:1000:3]
>>> [4, 7, 10]
a_list[::-1]
>>> [10, 9, 8, 7, 6, 5, 4, 3]
a_list[3:1]
>>> []
Practice Exercises
animal = ['e', 'l', 'e', 'p', 'h', 'a', 'n', 't']. Use slicing in different ways if possible to answer the following. - Get the first four letters.
- Get the last three letters.
- Get the every second letter starting from index 1.
- Get the all letters except the first and last.
- Get the middle three letters.
- Get an empty list.
- Reverse the letters.
- Get the letters h, p, e .
- What does
animal[1:-1:2]return?
$+$ and $*$ Operators
The $+$ operator is to concatenate lists. The $*$ operator is to make copies of a list and concatenate them together. The original list remains unchanged unless it is reassigned to the result of the addition or multiplication.
list_1 = [7, -5, 8]
list_2 = [5, 4.7, -2, "hello", 0, True, -1000.1]
# test
addition = list_1 + list_2
addition
>>> [7, -5, 8, 5, 4.7, -2, "hello", 0, True, -1000.1, 5, 5, 5]
multiplication = list_1 * 3
multiplication
>>> [7, -5, 8, 7, -5, 8, 7, -5, 8]
The $*$ operator can be used when we want a list to be initialized with some default value. For example, a teacher is making a file to input students’ quiz grades later. They can do quizzes = [-1] * 50 to avoid typing one number too many times and know which grades have been updated.
Mutable and Immutable
Practice Exercises
Point out the differences between the functions below. Is the original list updated in both? Explain why or why not. How is it related to immutable and mutable here? What are pros and cons of each? In which cases should we use them?| Immutable | Mutable |
|---|---|
|
|
Pros:
| Pros:
|
Cons:
| Cons:
|
Methods
list_name.method_name()
Classes which we’ll learn in Python for AI 105 can be used to implement data structures. A class of list includes certain methods, so lists can access all of them. Some methods update a list when applied, which avoids the need to assign a new variable and thus saves memory.
We’ll use a_list = [4, 5, 6, 7, 4, 7] for examples below.
| Methods | Purposes | Returned Value | List Updated | Examples |
|---|---|---|---|---|
| append() | Add a given value to the end | None | a_list.append(10) a_list >>> [4, 5, 6, 7, 4, 7, 10] | |
| insert() | Add a given value to the position of a given index | None | a_list.insert(2,10) a_list >>> [4, 5, 10, 6, 7, 4, 7] | |
| extend() | Concatenate a given list to the original | None | a_list.extend([10, 8]) a_list >>> [4, 5, 6, 7, 4, 7, 10, 8] | |
| pop() | Delete the last element by default or delete by index | Value | a_list.pop(1) >>> 5 a_list >>> [4, 6, 7, 4, 7] | |
| remove() | Delete the first occurence of a given value | None | a_list.remove(7) a_list >>> [4, 5, 6, 4, 7] | |
| del | Delete a given list of elements | N/A | del a_list[1:4] a_list >>> [4, 7, 7] | |
| clear() | Delete all elements | None | a_list.clear() a_list >>> [] | |
| index() | Return the index of the first occurence of a given value | Index | a_list.index(7) >>> 3 a_list >>> [4, 5, 6, 7, 4, 7] | |
| reverse() | Reverse elements | None | a_list.reverse() a_list >>> [7, 4, 7, 6, 5, 4] | |
| sort() | Arrange elements in an increasing order by default | None | a_list.sort() a_list >>> [4, 4, 5, 6, 7, 7] | |
| count() | Return the number of a given value | Number of times an element appears | a_list.count(7) >>> 2 a_list ` »> [4, 5, 6, 7, 4, 7]` | |
| copy() | Make a copy of the list | New list that has the same value as the original | a_list.copy() >>> [4, 5, 6, 7, 4, 7] a_list >>> [4, 5, 6, 7, 4, 7] |
list_name.copy() and direct assigment of one list to another operate differently behind the scenes.
| Tasks | copy() Method | Assignment (=) |
|---|---|---|
| Code |
|
|
| Rendered output |
|
|
| Explanation |
|
|
Pro tip: For newbies, try to create built-in methods.
In fact, we are able to write our own functions that replicate the behavior of these methods using only basic concepts we have learned so far.
Practice Exercises
def my_insert(data, index, value):
length = len(data)
result = []
for i in range(length):
if i == index:
data.append(value)
data.append(data[i])
data.append(data[i])
return result
Exercise 2: Write different functions that act like insert(), at least one with returning a value and one without. Exercise 3: Write functions for other methods if possible.
Exercise 4:
remove() only deletes the first occurence of a given value. Can you write a function that deletes all the occurences of a given value? If yes, write the function. Or else, explain why not. Exercise 4: What is the output of the following?
data1 =[1, 2, 3]
data2 = data1
data3 = data1.copy()
data1[0] = 9
print(data1[0], data2[0], data3[0])
Built-in Functions
| Functions | Purposes | Returned Value | List Updated | Examples |
|---|---|---|---|---|
| len() | Return the number of elements | Quantity | len(a_list) >>> 6 | |
| min() | Return the minimum element | Quantity | min(a_list) >>> 4 | |
| max() | Return the maximum element | Quantity | max(a_list) >>> 7 | |
| sorted() | Arrange elements in an ascending order by default | New List | sorted_a_list = sorted(a_list) sorted_a_list >>> [4, 4, 5, 6, 7, 7] | |
| sum() | Return the sum of all elements | Quantity | sum(a_list) >>> 33 | |
| reversed() | Reverse elements | A reverse iterator object | ||
| enumerate() | Return a sequence of tuples of matching index and value | An enumerate object | ||
| zip() | Take two lists and return an iterator of tuples of values with matching index | A zip object |
enumerate() replaces a regular for loop to access indices and values in a list, speeding up the coding process.
data_0 = [4, 5, 6]
# print index and value in pairs using enumerate()
for index, value in enumerate(data_0):
print(index, value)
# The rendered output is
0 4
1 5
2 6
zip() acts like a zipper that interlocks two rows of teeth. Difference in length of the rows doesn’t matter; it can zip to the end of the shorter one. We use this method when working with data in pairs.
data_1 = [1, 2, 3]
data_2 = ['john', 'mary', 'peter']
# print in pairs using zip()
for position, name in zip(data_1, data_2):
print(v1, v2)
# The rendered output is
1 john
2 mary
3 peter
Practice Exercises
Exercise 2: Write a function that sums up even and odd numbers.
Exercise 3: Write a function that sums up elements with even and odd indices.
List Comprehension
list_name = [condition_to_branch_x for x in data condition_to_filter_x]
List comprehension is favored among programmers, allowing us to write a simple for loop in only one line. A typical list comprehension has three parts as above.
You may ask: Do I need all parts for list comprehension? How am I reading this, from left to right?
A short answer for the first question is no; we’ll discuss the details in a moment. For the second question, well yeah if we read it like an English text block. But, this is Python language; we read list comprehension in the following order.
- Looping
for x in data. - Filtering
condition_to_filter_x. - Routing
condition_to_branch_x.
Imagine a company is hiring summer interns using a list of job-seekers. They need to evaluate all of them one by one. They can either accept all the candidates or filter some out.
(Optional) condition_to_filter
if x_filter_condition
The filter condition can only have if. Here the company decides to be selective, setting up an interview for each applicant. If someone passes the interview, they proceed with the internship. Otherwise, they’re out.
(Required) condition_to_branch_x
[if_value if x_if_condition else else_value for x in data]
[if_value if x_if_condition else elif_value if x_elif_condition else else_value for x in data]
The branch condition must be either if-else or if-elif-else. For the latter, it’s written as nested if-else. A syntax error will be raised if an else is missing.
Now, for those joining the internship of the company, the supervisor must assign them jobs based on their qualifications. This makes it logical to have if-else or if-elif-else in this part. Besides, without an else, it means an incoming intern who has got hired must still be qualified for something else to be hired again. What about those who cannot meet that “something else”? That makes no sense.
Practice Exercises
data = [1, 2]
for x in data:
x = 20
print(f"data is {data}")
Exercise 2: What is the output for a_list = [4, 5, 6, 7, 4, 7]? - a_list.sort(rever = True)
- sorted(a_list, reverse = True)
Exercise 4: Write the following activation functions for lists using list comprehension. Write helper functions when needed.
- Sigmoid function
- ReLU function
print(data_a) for each list comprehension line below given data = [1, 5, -4, 2, 3, -2, 8, 7]? Explain your answer.
data_a = [x if x > 0 else 0 for x in data]
data_a = [x if x > 0 for x in data]
data_a = [x for x in data if x > 0]
data_a = [x for x in data if x > 0 else 0]
data_a = [x if x%2==0 else 0 for x in data if x > 4]
2D List
Real-world data extensively need more-than-one-dimension structure/matrix to represent it. For instance, color image data (at least 3D), gray scale image data (2D), and text data (2D). Later on, we’ll use a package called NumPy for these scenarios but use list for now.
We’ll focus on two dimensions here. To describe a 2D matrix, we can use a nested list—a list where each element is a list of the same size. Take data_2d = [[1, 2, 3], [4, 5, 6]].
| Plain Language | Python Language |
|---|---|
| data_2d is a box | data_2d points to a list object |
| the box holds 2 name tags | a list object contains two pointers |
| the first tag says “look over here”, which directs to a baby box named [1, 2, 3] | the element of index 0 points to another list object with value [1, 2, 3] |
| the second tag says “look over there”, which directs to another baby box named [4, 5, 6] | the element of index 1 points to another list object with value [4, 5, 6] |
Since each element of a nested 2D list is a list itself, we can access the inner elements using the same indexing syntax as with regular lists. For example, data[1][2] = 6.
A nested 2D list can be thought as a matrix. Each outter element is a row and the columns are formed by items of the same index in each inner list. So, the number of rows is the number of outer elements while the number of columns is the size of the inner list. In this case, this matrix has 2 rows and 3 columns.
data_2d = [[1, 2, 3], [4, 5, 6]]
num_rows = len(data_2d)
num_cols = len(data_2d[0])
# iterate through a nested list using a nested loop and print the matrix
for i in range(num_rows):
for j in range(num_cols):
# print each row
print(data_2d[i][j], end = ' ')
print()
'''
The rendered output is
1 2 3
4 5 6
'''
Practice Exercises
Exercise 2: Use exercise 1 to write a function that prints a 2D matrix whose elements are square of elements of a nested list with or without returning a value.
- a_list.sort(reverse = True)
- sorted(a_list, reverse = True)
Exercise 4: Use exercise 1, what is the output?
width = 2
height = 3
matrix = [[0, 0], [0, 0], [0, 0]]
for i in range(height):
for j in range(width):
matrix[i][j] = i * width + j
print_matrix(matrix)
width = 3
height = 3
matrix = [[1, 1, 1], [1, 0, 0], [1, 0, 0]]
for i in range(1, height):
for j in range(1, width):
matrix[i][j] = matri[i-1][j] + matrix[i][j-1] + matrix[i-1][j - 1]
print_matrix(matrix)
Algorithm and Case Study
Case Study: List and Integral
Breakdown:
- $f(x)$ is the value of the element at index $x$.
- $F(x)$ is the sum of a matrix---all elements from index $0$ to $x$.
- $A$ is the sum of the submatrix---all the elements from one position to another.
- Let's go over integral method in Calculus first.
- $F(a) = \int_{-\infty}^{a} f(x)dx = \sum_{\Delta{x} \to 0} f(x) \Delta{x}$: summation of rectangle areas
- $F(b) = \int_{-\infty}^{b} f(x)dx = \sum_{\Delta{x} \to 0} f(x) \Delta{x}$
- $A = \int_{a}^{b} f(x)dx = F(b) - F(a)$.
- As $c$ is very close to $a$, $F(a) = \int_{-\infty}^{a} f(x)dx + f(c)dx = F(a) + f(c)dx$.
- Applied to our problem, we have
- $F(x) = F(x-1) + f(x)$
- $A = \sum_{a}^{b} f(x) = F(b) - F(a-1)$: summation from index $a$ to $b$.
Assume the calculation of $F(x) $ is provided, we just need to know the values of two positions to complete the task. - The cost to perform this method is low as we can avoid running through the whole submatrix and do accumulation.
Practice Exercise
index() using enumerate(). This exercise illustrates Linear Searching Algorithm on List, an algorithm for finding an element that equals some given value by searching through elements of a list one by one.
Exercise 2: Write a function that mimics
sort() using min(), remove(), append(). Exercise 3: Provide an idea for the case study with a 2D matrix.
FAQ
Here are some good questions asked during class. The answers to these questions came from either some admin, TA, STA, or members of AIO2025.
| Questions | Answers |
|---|---|
| What is an embedded system? | No. Python reads code top-down. |
| Can I write 2 functions A and B where A calls B and B calls A? Why does it give an error when I refer to one before defining it? | Yes. Such a situation is called mutual recursion. Python doesn’t execute function definitions which consist of function references. It only starts evaluating when a function is called. |
| A resource for mutable and immutable. | https://www.geeksforgeeks.org/mutable-vs-immutable-objects-in-python/ |
| A resource for pass by reference and pass by value | https://www.tutorialspoint.com/python/python_functions.htm |
| How do I tell when two variables point to the same object in memory? | You either need to understand what’s going on very well or check with the id() function. Each object has one and only one identity. |
| Can I use list comprehension to create a completely new list? | Yes. It all depends on how you choose your values inside the branch condition. |
| Visualization for Sorted List | https://www.cs.usfca.edu/~galles/visualization/Search.html |
| Are integral image and box filtering similar? | Yes. Box filtering is in graphics. |
Enjoy Reading This Article?
Here are some more articles you might like to read next: