Cách Tăng Tốc Độ Xử Lý Đối Tượng Sequence Type

Cách Tăng Tốc Độ Xử Lý Đối Tượng Sequence Type

Trong bài viết trước, tôi đã giới thiệu về built-in function và đưa ra một số ví dụ về việc dùng built-in function sẽ đẩy nhanh tốc độ thực thi của chương trình.
Bài viết tiếp theo này, tôi sẽ giới thiệu tiếp một số built-in functions thường được các lập trình viên Python "ưu ái" sử dụng khi xử lý các logic liên quan đến các đối tượng của kiểu dữ liệu lặp (sequence type object) như list/set/dict/string/tuple.

Đặc điểm của các đối tượng của kiểu dữ liệu lặp là cho phép lập trình viên lấy ra từng phần tử trong nó, hành động lấy ra các đối tượng có thể lặp đi lặp lại một cách tuần tự bằng vòng lặp for, while, next, ....

Ở bài viết trước, tôi đã đưa ra các ví dụ về tốc độ xử lý của 2 built-in function sum và len, bài viết tiếp theo này, tôi sẽ bỏ qua việc chứng minh tốc độ thực thi của built-in function mà tập trung vào chức năng và cách sử dụng của built-in function.

1. sorted

sorted(iterable, key=key, reverse=reverse)

Đầu vào:
- iterable: Một sequence-type-object
- key: Tham số tùy chọn, cho phép sắp xếp sequence-type-object theo một tiêu chí nào đó.
- reverse: Tham số tùy chọn, mặc định là False; Nếu False thì sắp xếp theo tăng dần, True sắp xếp theo giảm dần
Đầu ra: Một sequence-type-object đã được sắp xếp theo thứ tự (tăng dần hoặc giảm dần) và theo tiêu chí key.

Function đầu tiên mà tôi muốn giới thiệu với các bạn là function sorted, đây là một built-in function sẽ giúp các lập trình viên tiết kiệm được nhiều số dòng code và giải quyết được một số bài toán đặc biệt.

Ví dụ:

numbers = [63, 83, 32, 80, 55, 68, 28, 21, 60, 89]
ascending_numbers = sorted(numbers)
descending_numbers = sorted(numbers, reverse=True)

print("numbers:", numbers)
print("ascending_numbers: ", ascending_numbers)
print("descending_numbers: ", descending_numbers)

Kết quả: 

numbers: [63, 83, 32, 80, 55, 68, 28, 21, 60, 89]
ascending_numbers: [21, 28, 32, 55, 60, 63, 68, 80, 83, 89]
descending_numbers: [89, 83, 80, 68, 63, 60, 55, 32, 28, 21]

Lưu ý:
Trong các function được cung cấp bởi list object cũng có một function là list.sort().
Điều khác biệt giữa list.sort()sorted()list.sort() tác động trực tiếp vào list, thay đổi vị trí các phần tử trong list mà không thay đổi id của list.
Còn sorted() là trả về một list mới, có id khác với list đầu vào.

numbers = [63, 83, 32, 80, 55, 68, 28, 21, 60, 89]
print("before sort()")
print("numbers:", numbers)
print("id of numbers:", id(numbers))

sorted_numbers = sorted(numbers)
numbers.sort()

print("---------------------")
print("after sort()")
print("numbers:", numbers)
print("id of numbers:", id(numbers))

print("---------------------")
print("sorted_numbers:", sorted_numbers)
print("id of sorted_numbers:", id(sorted_numbers))

Kết quả:

before sort()
numbers: [63, 83, 32, 80, 55, 68, 28, 21, 60, 89]
id of numbers: 2209690579392
---------------------
after sort()
numbers: [21, 28, 32, 55, 60, 63, 68, 80, 83, 89]
id of numbers: 2209690579392
---------------------
sorted_numbers: [21, 28, 32, 55, 60, 63, 68, 80, 83, 89]
id of sorted_numbers: 2209692170432

Dùng list.sort() hay sorted() tùy vào nhu cầu của lập trình viên.

Cách sử dụng key trong sorted khá uyển chuyển, khi lập trình viên đưa ra một điều kiện gắn vào với key, mỗi lần duyệt qua một phần tử trong sequence-type-object, sẽ phát sinh một lần tính toán theo điều kiện và dùng kết quả của phép tính toán đó để thực hiện sắp xếp trong sequence-type-object.

Định nghĩa về cách sử dụng key này hơi khó hiểu, chúng ta đến một ví dụ sau để dễ hiểu hơn.

Đề bài:
Cho một danh sách tên một số loại hoa quả, hãy sắp xếp danh sách theo thứ tự tăng dần của độ dài (số lượng ký tự) các loại hoa quả.

fruits = ['banana', 'apple', 'orange', 'lemon', 'pear', 'grape', 'blackberries', 'coconut', 'papaya']
sorted_fruits = sorted(fruits, key=len)

print("before sort", fruits)
print("after sort", sorted_fruits)

before sort ['banana', 'apple', 'orange', 'lemon', 'pear', 'grape', 'blackberries', 'coconut', 'papaya']
after sort ['pear', 'apple', 'lemon', 'grape', 'banana', 'orange', 'papaya', 'coconut', 'blackberries']

Bài toán trên nếu để làm theo cách lập trình thông thường (kể cả dùng python để giải quyết) cũng sẽ tốn khá khá "giấy bút" và các biến dữ liệu phụ.
Nhưng dùng built-in function chỉ tốn đúng 01 dòng.

Tiếp tục đi vào ví dụ thứ 2 để nhấn mạnh thêm tiện ích của key nhé.

Đề bài:
Cho một list các tuple chứa 3 số. Hãy sắp xếp các tuple trong list theo thứ tự tăng dần của phần tử thứ 2 trong tuple.

tuples =  [(43, 91, 26), (52, 7, 98), (47, 28, 44), (10, 20, 26), (77, 7, 53)]

def get_key(tuple_item):
    return tuple_item[1]

sorted_tuples = sorted(tuples, key=get_key)

print("before sort", tuples)
print("after sort", sorted_tuples)

before sort [(43, 91, 26), (52, 7, 98), (47, 28, 44), (10, 20, 26), (77, 7, 53)]
after sort [(52, 7, 98), (77, 7, 53), (10, 20, 26), (47, 28, 44), (43, 91, 26)]

Ngoài cách viết trên, còn cách viết ngắn gọn hơn dùng đến biểu thức lambda

sorted_lambda = sorted(tuples, key=lambda x: x[1])

2. Enumerate

Đây là một function được đưa ra để giúp các lập trình viên thực hiện lấy ra cặp giá trị index-value từ các đối tượng sequence-type-object (list, set, dict, string,...)

Sử dụng enumerate sẽ thay thế được cú pháp lấy ra index-value từ đối tượng sequence-type-object thường gặp với C-style.

# basic looping
code_learn = "https://codelearn.io/"

for index in range(len(code_learn)):   
    print("index:", index, "value:", code_learn[index])

# use enumerate function
for index, value in enumerate(code_learn):
    print("index:", index, "value:", value)
  

Câu hỏi đặt ra là đối tượng trả về của function enumerate là gì ? và từng đối tượng được sử dụng trong vòng lặp for phía trên là kiểu dữ liệu gì?

numbers = ["1", "2", "3", "4"]
enumerate_object = enumerate(numbers)
print("type enumerate object: ", enumerate_object)

for item in enumerate_object:
    print(item, type(item))

Kết quả: 

type enumerate object: <enumerate object at 0x0000016D42C61090>
(0, '1') <class 'tuple'>
(1, '2') <class 'tuple'>
(2, '3') <class 'tuple'>
(3, '4') <class 'tuple'>

Như vậy kết quả trả về của enumerate là đối tượng enumerate object và từng đối tượng được lấy ra có kiểu dữ liệu tuple.

Ngoài việc sử dụng trong vòng lặp, enumerate có thể giúp các lập trình viên tạo ra một list dữ liệu chứa các tuple:

numbers = ["1", "2", "3", "4"]
enumerate_object = enumerate(numbers)
list_tuple = list(enumerate(numbers))
print(list_tuple)

Kết quả:
[(0, '1'), (1, '2'), (2, '3'), (3, '4')]

Trong định nghĩa của enumerate, có tham số start, với giá trị mặc định start=0 nghĩa là giá trị index trong tuple được lấy ra đầu tiên sẽ được đánh giá trị bắt đầu từ 0. Nếu thay đổi giá trị start thì giá trị index trong tuple đầu tiên sẽ tương ứng với giá trị được thay đổi.

 

numbers = ["1", "2", "3", "4"]
for index, value in enumerate(numbers, start=2):
    print("index:", index, "value:", value)

Kết quả:
index: 2 value: 1
index: 3 value: 2
index: 4 value: 3
index: 5 value: 4

3. all & any

3.1. all

Đầu vào: Một sequence-type-object
Đầu ra: Trả về True nếu tất cả các phần tử trong sequence-type-object đều có giá trị True. Nếu không, trả về False

Trong Python, các giá trị được tính là có giá trị False (falsy values)
- None
- False (kiểu dữ liệu Boolean)
- Số 0
- Đối tượng list, set, tuple, dict rỗng

Nếu không sử dụng function all(), chúng ta sẽ phải thực hiện một vòng lặp để lặp qua tất cả các phần tử và so sánh

booleans = [True, True, True, False]

# basic looping
def is_contain_falsy_value(input_sequence-type-object):
    for value in input_sequence-type-object:
        if not value:
            return False
    return True

print("basic looping:", is_contain_falsy_value(booleans))

# all function
print("all function:", all(booleans))

Kết quả trả về:
basic looping: False
all function: False


Các ví dụ về dùng all()

print(all([1, 2, 3, 4, 5]))
print(all([0, 1, 2, 3, 4, 5]))
print(all(('Hà Nội', 'Huế', 'Đà Nẵng')))
print(all(('Hà Nội', 'Huế', 'Đà Nẵng', '')))

Kết quả:
True
False
True
False

Với dictionary, function all sẽ thực hiện lấy các giá trị của key trong dictionary để tính toán kết quả trả về.

my_dict = {
    1: "số 1",
    2: "số 2",
    3: "số 3"
}
print(all(my_dict))

Kết quả:
True

my_dict = {
    0: "số 0",
    1: "số 1",
    2: "số 2",
    3: "số 3"
}
print(all(my_dict))

Kết quả:
False

3.2. any

Đầu vào: Một sequence-type-object
Đầu ra: Trả về True nếu chỉ cần một phần tử trong sequence-type-object có giá trị True. Nếu không, trả về False

Hàm any hơi khác một chút so với hàm all. Hàm all() chỉ cần có 1 giá trị là False sẽ trả về False, hàm any thì chỉ cần một phần tử giá trị True là trả về True

# iterable - a list of booleans
any([True, False, False])
# True
any([False, False, False])
# False

# iterable - a list of integers (0 is considered false - falsy value)
any([0, 1, 2])
# True
any([0, 0, 0])
# False

# iterable - a tuple of string (an empty string is considered false)
any(('Hà Nội', '', ''))
# True
any(('', '', ''))
# False

# iterable - a nested list (an empty list is considered false)
any([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# True
any([[], [], []])
# False

Tương tự với all khi ứng xử với dictionary, any cũng nhận đầu vào key của các dictionary

my_dict = {
    0: "số 0",
    1: "số 1",
    2: "số 2",
    3: "số 3"
}

any(my_dict)
# True

4. Reversed

reversed(sequence): Đảo ngược các phần tử trong một sequence-type-object

Đầu vào: sequence-type-object
Đầu ra: sequence-type-object có type là list_reversesequence-type-object.

original_list = [1, 2, 3, 4]
reversed_sequence = reversed(original_list)
print("type", type(reversed_sequence))
print("sequence-type-object", reversed_sequence)

for number in reversed_sequence:
    print(number, end=", ")
print()

reversed_list = list(reversed(original_list))

print("reversed_list", reversed_list)

Kết quả: 

type <class 'list_reverseiterator'>
sequence-type-object <list_reverseiterator object at 0x000002027C0AF220>
4, 3, 2, 1, 
reversed_list [4, 3, 2, 1]


Sử dụng built-in function reversed sẽ giúp chúng ta lấy ra nhanh một đối tượng đảo ngược của đối tượng gốc thay vì sử dụng vòng lặp để append dần các đối tượng từ cuối lên đầu hoặc sử dụng slice [::-1].

5. max & min

max(sequence-type-object): Trả về số lớn nhất trong các số thuộc sequence-type-object
min(sequence-type-object): Trả về số nhỏ nhất trong các số thuộc sequence-type-object

Riêng với 2 function này chắc không cần nói nhiều về chúng, bài toán tìm số lớn nhất & nhỏ nhất tốn rất nhiều "giấy bút" của các lập trình viên và Python đã cung cấp luôn 2 built-in function để giải quyết bài toán này.

Trên thực tế, với một số tập dữ liệu đặc biệt có thể built-in function cho kết quả chưa chắc đã tối ưu hơn các cách giải quyết bài toán bằng tư duy toán học & thuật toán nhưng bằng việc chỉ dùng 1 dòng lệnh để tính ra kết quả thì cũng xứng đáng để chúng ta sử dụng 2 function này

numbers = [4, 52, 97, 24, 48]

min_number = min(numbers)
max_number = max(numbers)

print("min:", min_number)
print("max:", max_number)

Kết quả: 

min: 4
max: 97

Kết luận

Trên đây là một số ví dụ về built-in function thường dùng trong xử lý các kiểu dữ liệu lặp, hy vọng các bạn sẽ rút ra được một số kinh nghiệm cho bản thân mình và lựa chọn đúng đắn trong việc tự code hay sử dụng luôn built-in function.
Cảm ơn các bạn đã đọc bài viết.