Working with Lists in Python - Part 1

by Pyrastra Team
Working with Lists in Python - Part 1

Before starting this lesson, let’s give everyone a programming task: roll a die 6000 times and count the number of times each point appears. This task should be very simple for everyone. We can use uniformly distributed random numbers from 1 to 6 to simulate rolling a die, then use 6 variables to record the number of times each point appears. I believe that through previous learning, everyone can write the following code relatively smoothly.

"""
Roll a die 6000 times and count the number of times each point appears

Author: Luo Hao
Version: 1.0
"""
import random

f1 = 0
f2 = 0
f3 = 0
f4 = 0
f5 = 0
f6 = 0
for _ in range(6000):
    face = random.randrange(1, 7)
    if face == 1:
        f1 += 1
    elif face == 2:
        f2 += 1
    elif face == 3:
        f3 += 1
    elif face == 4:
        f4 += 1
    elif face == 5:
        f5 += 1
    else:
        f6 += 1
print(f'1 appeared {f1} times')
print(f'2 appeared {f2} times')
print(f'3 appeared {f3} times')
print(f'4 appeared {f4} times')
print(f'5 appeared {f5} times')
print(f'6 appeared {f6} times')

I don’t need to say much about how “ugly” the code above is. Of course, what’s even more terrible is that if we want to roll two or more dice and then count the number of times each point appears, we would need to define more variables and write more branch structures. Just thinking about it makes you feel sick. At this point, I believe everyone already has a question: Is there a way to use one variable to save multiple data, and is there a way to use unified code to operate on multiple data? The answer is yes. In Python, we can save and operate on multiple data through container-type variables. We’ll first introduce a new data type called list.

Creating Lists

In Python, a list is a data sequence composed of a series of elements in a specific order. This means that if we define a list-type variable, we can use it to save multiple data. In Python, we can use [] literal syntax to define lists, with multiple elements in the list separated by commas, as shown in the code below.

items1 = [35, 12, 99, 68, 55, 35, 87]
items2 = ['Python', 'Java', 'Go', 'Kotlin']
items3 = [100, 12.3, 'Python', True]
print(items1)  # [35, 12, 99, 68, 55, 35, 87]
print(items2)  # ['Python', 'Java', 'Go', 'Kotlin']
print(items3)  # [100, 12.3, 'Python', True]

Note: Lists can have duplicate elements, such as 35 in items1; lists can have elements of different types, such as items3 which has int, float, str, and bool type elements. However, we generally don’t recommend putting elements of different types in the same list, mainly because it’s extremely inconvenient to operate.

We can use the type function to check the type of a variable. Interested readers can check what type the variable items1 above is. Because lists can save multiple elements, they are a container-type data type. So when naming list-type variables, variable names usually use plural words.

In addition, we can also convert other sequences into lists through Python’s built-in list function. To be precise, list is not an ordinary function; it’s a constructor for creating list objects. We’ll introduce concepts like objects and constructors in later lessons.

items4 = list(range(1, 10))
items5 = list('hello')
print(items4)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(items5)  # ['h', 'e', 'l', 'l', 'o']

Note: range(1, 10) produces an integer sequence from 1 to 9. When given to the list constructor, it creates a list composed of integers from 1 to 9. A string is a sequence composed of characters. The list('hello') above uses the characters of the string hello as list elements to create a list object.

List Operations

We can use the + operator to concatenate two lists. The concatenation operation connects elements from two lists into one list, as shown in the code below.

items5 = [35, 12, 99, 45, 66]
items6 = [45, 58, 29]
items7 = ['Python', 'Java', 'JavaScript']
print(items5 + items6)  # [35, 12, 99, 45, 66, 45, 58, 29]
print(items6 + items7)  # [45, 58, 29, 'Python', 'Java', 'JavaScript']
items5 += items6
print(items5)  # [35, 12, 99, 45, 66, 45, 58, 29]

We can use the * operator to implement list repetition operations. The * operator repeats list elements a specified number of times. Let’s add two lines to the code above, as shown below.

print(items6 * 3)  # [45, 58, 29, 45, 58, 29, 45, 58, 29]
print(items7 * 2)  # ['Python', 'Java', 'JavaScript', 'Python', 'Java', 'JavaScript']

We can use the in or not in operators to determine whether an element is in a list. Let’s add two more lines to the code above, as shown below.

print(29 in items6)  # True
print(99 in items6)  # False
print('C++' not in items7)     # True
print('Python' not in items7)  # False

Since there are multiple elements in a list and elements are placed in the list in a specific order, when we want to operate on a certain element in the list, we can use the [] operator. By specifying the element’s position in [], we can access that element. This operation is called indexing. It should be noted that the element position in [] can be an integer from 0 to N - 1, or an integer from -1 to -N, called forward indexing and reverse indexing respectively, where N represents the number of list elements. For forward indexing, [0] can access the first element in the list, and [N - 1] can access the last element. For reverse indexing, [-1] can access the last element in the list, and [-N] can access the first element, as shown in the code below.

items8 = ['apple', 'waxberry', 'pitaya', 'peach', 'watermelon']
print(items8[0])   # apple
print(items8[2])   # pitaya
print(items8[4])   # watermelon
items8[2] = 'durian'
print(items8)      # ['apple', 'waxberry', 'durian', 'peach', 'watermelon']
print(items8[-5])  # 'apple'
print(items8[-4])  # 'waxberry'
print(items8[-1])  # watermelon
items8[-4] = 'strawberry'
print(items8)      # ['apple', 'strawberry', 'durian', 'peach', 'watermelon']

When using indexing operations, avoid index out of bounds situations. For the items8 above, if we access items8[5] or items8[-6], it will raise an IndexError, causing the program to crash. The corresponding error message is: list index out of range, which translates to “array index out of range”. For a list with only five elements like items8, valid forward indices are 0 to 4, and valid reverse indices are -1 to -5.

If we want to access multiple elements in a list at once, we can use slicing operations. Slicing is an operator in the form [start:end:stride], where start represents the starting position for accessing list elements, end represents the ending position for accessing list elements (the element at the ending position cannot be accessed), and stride represents the stride, simply put, the position increment. For example, if the first element we access is at the start position, then the second element is at the start + stride position, and of course start + stride must be less than end. Let’s add the following statements to the code above to use the slicing operator to access list elements.

print(items8[1:3:1])     # ['strawberry', 'durian']
print(items8[0:3:1])     # ['apple', 'strawberry', 'durian']
print(items8[0:5:2])     # ['apple', 'durian', 'watermelon']
print(items8[-4:-2:1])   # ['strawberry', 'durian']
print(items8[-2:-6:-1])  # ['peach', 'durian', 'strawberry', 'apple']

Reminder: You can look at the last line in the code above and think about how slicing operations access elements when the stride is negative.

If the start value equals 0, it can be omitted when using the slicing operator; if the end value equals N, where N represents the number of list elements, it can be omitted when using the slicing operator; if the stride value equals 1, it can also be omitted when using the slicing operator. So the code below has exactly the same effect as the code above.

print(items8[1:3])     # ['strawberry', 'durian']
print(items8[:3:1])    # ['apple', 'strawberry', 'durian']
print(items8[::2])     # ['apple', 'durian', 'watermelon']
print(items8[-4:-2])   # ['strawberry', 'durian']
print(items8[-2::-1])  # ['peach', 'durian', 'strawberry', 'apple']

In fact, we can also modify elements in a list through slicing operations. For example, let’s add one more line to the code above. You can see the output here.

items8[1:3] = ['x', 'o']
print(items8)  # ['apple', 'x', 'o', 'peach', 'watermelon']

Two lists can also perform relational operations. We can compare whether two lists are equal, and we can also compare the size of two lists, as shown in the code below.

nums1 = [1, 2, 3, 4]
nums2 = list(range(1, 5))
nums3 = [3, 2, 1]
print(nums1 == nums2)  # True
print(nums1 != nums2)  # False
print(nums1 <= nums3)  # True
print(nums2 >= nums3)  # False

Note: The corresponding elements of nums1 and nums2 above are completely the same, so the result of the == operation is True. For the comparison of nums2 and nums3, since the first element 1 of nums2 is less than the first element 3 of nums3, the result of nums2 >= nums3 comparison is False. Relational operations on two lists are not that commonly used in actual work. If you really don’t understand, just skip it; don’t worry about it.

Element Iteration

If you want to extract elements from a list one by one, you can use a for-in loop. There are two ways to do this.

Method 1: Iterate through list elements using indexing operations in the loop structure.

languages = ['Python', 'Java', 'C++', 'Kotlin']
for index in range(len(languages)):
    print(languages[index])

Output:

Python
Java
C++
Kotlin

Note: The len function above can get the number of list elements N, and range(N) constructs a range from 0 to N-1, which can just be used as list element indices.

Method 2: Loop directly over the list, with the loop variable representing list elements.

languages = ['Python', 'Java', 'C++', 'Kotlin']
for language in languages:
    print(language)

Output:

Python
Java
C++
Kotlin

Summary

At this point, we can use list knowledge to refactor the code for “rolling a die and counting the number of times each point appears” above.

"""
Roll a die 6000 times and count the number of times each point appears

Author: Luo Hao
Version: 1.1
"""
import random

counters = [0] * 6
# Simulate rolling a die and record the number of times each point appears
for _ in range(6000):
    face = random.randrange(1, 7)
    counters[face - 1] += 1
# Output the number of times each point appears
for face in range(1, 7):
    print(f'{face} appeared {counters[face - 1]} times')

In the code above, we use six elements in the counters list to represent the number of times points 1 to 6 appear, with all six element values initially being 0. Next, we use uniformly distributed random numbers from 1 to 6 to simulate rolling a die. If we roll 1 point, the value of counters[0] increases by 1; if we roll 2 points, the value of counters[1] increases by 1, and so on. Feel the difference - because we used list types plus loop structures, our data processing is batch-oriented, which makes the modified code much simpler and more elegant than the previous code.