Get started learning Python with DataCamp's free Intro to Python tutorial. Learn Data Science by completing interactive coding challenges and watching videos by expert instructors. Start Now!
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join 11 million other learners and get started learning Python for data science today!
Good news! You can save 25% off your Datacamp annual subscription with the code LEARNPYTHON23ALE25 - Click here to redeem your discount
Bản đồ, Bộ lọc, Giảm thiểu
Map, Filter, và Reduce là những mô hình của lập trình hàm. Chúng cho phép lập trình viên (bạn) viết mã đơn giản và ngắn gọn hơn, mà không cần quá bận tâm về các chi tiết như vòng lặp và nhánh.
Về cơ bản, ba hàm này cho phép bạn áp dụng một hàm trên một số lượng các iterable, chỉ trong một bước. map
và filter
được tích hợp sẵn trong Python (trong module __builtins__
) và không cần import. reduce
, tuy nhiên, cần được import vì nó nằm trong module functools
. Hãy hiểu rõ hơn về cách chúng hoạt động, bắt đầu với map
.
Map
Hàm map()
trong python có cú pháp như sau:
map(func, *iterables)
Trong đó func
là hàm mà mỗi phần tử trong iterables
(nhiều đến chừng nào cũng được) sẽ được áp dụng. Lưu ý dấu sao (*
) trên iterables
? Nó có nghĩa là có thể có bao nhiêu iterable cũng được, miễn là func
có số lượng tham số đầu vào chính xác như vậy. Trước khi chúng ta đi vào một ví dụ, điều quan trọng cần lưu ý là:
- Trong Python 2, hàm
map()
trả về một danh sách. Tuy nhiên, trong Python 3, hàm này trả về mộtmap object
là một đối tượng generator. Để nhận kết quả dưới dạng danh sách, hàm tích hợp sẵnlist()
có thể được gọi trên đối tượng map. Tức làlist(map(func, *iterables))
- Số đối số cho
func
phải bằng số lượng của nhữngiterables
được liệt kê.
Hãy xem những quy tắc này được áp dụng như thế nào trong ví dụ sau đây.
Giả sử tôi có một danh sách (iterable
) tên của các thú cưng yêu thích của tôi, tất cả đều bằng chữ thường và tôi cần chúng ở dạng chữ hoa. Theo cách truyền thống, trong phong cách python thông thường, tôi sẽ làm như sau:
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = []
for pet in my_pets:
pet_ = pet.upper()
uppered_pets.append(pet_)
print(uppered_pets)
Mà sẽ xuất ra ['ALFRED', 'TABITHA', 'WILLIAM', 'ARLA']
Với các hàm map()
, điều đó không chỉ dễ dàng hơn mà còn linh hoạt hơn nhiều. Tôi chỉ cần làm:
# Python 3
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = list(map(str.upper, my_pets))
print(uppered_pets)
Mà cũng sẽ xuất ra kết quả tương tự. Lưu ý rằng với cú pháp map()
được định nghĩa ở trên, trong trường hợp này func
là str.upper
và iterables
là danh sách my_pets
-- chỉ một iterable. Cũng lưu ý rằng chúng tôi đã không gọi hàm str.upper
(làm như sau: str.upper()
), vì hàm map làm điều đó cho chúng ta trên mỗi phần tử trong danh sách my_pets
.
Điều quan trọng cần lưu ý là hàm str.upper
chỉ yêu cầu một đối số theo định nghĩa và vì vậy chúng ta chỉ truyền một iterable cho nó. Vì vậy, nếu hàm mà bạn đang truyền yêu cầu hai, ba, hoặc n đối số, thì bạn cần truyền vào hai, ba hoặc n iterable cho nó. Để làm rõ điều này, hãy xem một ví dụ khác.
Giả sử tôi có một danh sách diện tích hình tròn mà tôi đã tính ở đâu đó, tất cả có năm chữ số thập phân. Và tôi cần làm tròn mỗi phần tử trong danh sách lên đến chữ số thập phân của nó, có nghĩa là tôi phải làm tròn phần tử đầu tiên trong danh sách đến một chữ số thập phân, phần tử thứ hai trong danh sách hai chữ số thập phân, phần tử thứ ba trong danh sách ba chữ số thập phân, v.v. Với map()
, đây là điều dễ dàng. Hãy xem làm thế nào.
Python đã ban cho chúng ta hàm tích hợp sẵn round()
mà nhận hai đối số -- số để làm tròn và số chữ số thập phân để làm tròn số đó lên. Vì vậy, bởi vì hàm đó yêu cầu hai đối số, chúng ta cần truyền vào hai iterable.
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 7)))
print(result)
Thấy vẻ đẹp của map()
không? Bạn có thể tưởng tượng sự linh hoạt mà điều này gợi lên không?
Hàm range(1, 7)
hoạt động như là đối số thứ hai cho hàm round
(số chữ số thập phân cần thiết cho mỗi lần lặp). Vì vậy khi map
lặp qua circle_areas
, trong lần lặp đầu tiên, phần tử đầu tiên của circle_areas
, 3.56773
được truyền cùng với phần tử đầu tiên của range(1,7)
, 1
cho round
, khiến nó thực sự trở thành round(3.56773, 1)
. Trong lần lặp thứ hai, phần tử thứ hai của circle_areas
, 5.57668
cùng với phần tử thứ hai của range(1,7)
, 2
được truyền cho round
khiến nó trở thành round(5.57668, 2)
. Điều này xảy ra cho đến khi danh sách circle_areas
kết thúc.
Tôi chắc rằng bạn đang tự hỏi: "Điều gì sẽ xảy ra nếu tôi truyền vào một iterable ít hơn hoặc nhiều hơn độ dài của iterable đầu tiên? Đó là, điều gì sẽ xảy ra nếu tôi truyền range(1, 3)
hoặc range(1, 9999)
như là iterable thứ hai trong hàm trên". Và câu trả lời rất đơn giản: không gì cả! Được rồi, điều đó không đúng hoàn toàn. "Không gì" xảy ra theo nghĩa là hàm map()
sẽ không gây ra lỗi ngoại lệ nào cả, nó sẽ chỉ lặp qua các phần tử cho đến khi nó không thể tìm thấy một tham số thứ hai cho hàm, tại điểm đó nó đơn giản dừng lại và trả về kết quả.
Vì vậy, ví dụ, nếu bạn đánh giá result = list(map(round, circle_areas, range(1, 3)))
, bạn sẽ không gặp lỗi nào cả mặc dù độ dài của circle_areas
và range(1, 3)
khác nhau. Thay vào đó, đây là những gì Python làm: Nó lấy phần tử đầu tiên của circle_areas
và phần tử đầu tiên của range(1,3)
và truyền nó cho round
. round
đánh giá nó rồi lưu lại kết quả. Sau đó nó tiến đến lần lặp thứ hai, phần tử thứ hai của circle_areas
và phần tử thứ hai của range(1,3)
, round
lại lưu lại nó. Bây giờ, trong lần lặp thứ ba (circle_areas
có một phần tử thứ ba), Python lấy phần tử thứ ba của circle_areas
và cố gắng lấy phần tử thứ ba của range(1,3)
nhưng vì range(1,3)
không có một phần tử thứ ba, Python đơn giản dừng lại và trả về kết quả, mà trong trường hợp này chỉ là [3.6, 5.58]
.
Tiến hành thử xem.
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 3)))
print(result)
Điều tương tự cũng xảy ra nếu circle_areas
ít hơn độ dài của iterable thứ hai. Python đơn giản dừng lại khi nó không thể tìm thấy phần tử tiếp theo trong một trong những iterable.
Để củng cố kiến thức của chúng ta về hàm map()
, chúng ta sẽ sử dụng nó để triển khai hàm zip()
của riêng chúng ta. Hàm zip()
là một hàm có tác dụng lấy một số lượng các iterable và sau đó tạo ra một tuple chứa mỗi phần tử trong các iterable. Giống như map()
, trong Python 3, nó trả về một đối tượng generator, mà có thể dễ dàng chuyển đổi thành một danh sách bằng cách gọi hàm tích hợp sẵn list
trên nó. Sử dụng phiên làm việc diễn giải dưới đây để hiểu rõ về zip()
trước khi chúng ta tạo hàm chúng ta với map()
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(zip(my_strings, my_numbers))
print(results)
Như một phần thưởng, bạn có đoán được điều gì sẽ xảy ra trong phiên trên nếu my_strings
và my_numbers
không có cùng độ dài không? Không à? thử đi! Thay đổi độ dài của một trong số chúng.
Tiến đến hàm zip()
của riêng chúng ta!
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(map(lambda x, y: (x, y), my_strings, my_numbers))
print(results)
Chỉ cần nhìn vào đó thôi! Chúng ta có kết quả giống như zip
.
Bạn có nhận thấy rằng tôi thậm chí không cần tạo một hàm bằng cách sử dụng phương pháp tiêu chuẩn def my_function()
không? Đó là cách linh hoạt của map()
, và Python nói chung, là! Tôi chỉ đơn giản sử dụng một hàm lambda
. Điều này không có ý nói rằng việc sử dụng phương pháp định nghĩa hàm tiêu chuẩn (của def function_name()
) không được phép, nó vẫn được phép. Tôi chỉ đơn giản chọn viết ít mã hơn (theo "phong cách Python").
Đó là tất cả về map. Tiếp theo là filter()
Filter
Trong khi map()
truyền từng phần tử trong iterable qua một hàm và trả về kết quả của tất cả phần tử đã qua hàm, thì filter()
, trước tiên, yêu cầu hàm phải trả về các giá trị boolean (đúng hoặc sai) và sau đó truyền từng phần tử trong iterable qua hàm, "lọc" ra những phần tử mà sai. Nó có cú pháp như sau:
filter(func, iterable)
Những điểm sau cần được lưu ý về filter()
:
- Khác với
map()
, chỉ cần duy nhất một iterable. - Đối số
func
yêu cầu trả về kiểu boolean. Nếu không,filter
chỉ đơn giản trả vềiterable
đã truyền vào nó. Và vì chỉ cần duy nhất một iterable, điều đó ngụ ý rằngfunc
chỉ có thể lấy một đối số. filter
truyền từng phần tử trong iterable quafunc
và chỉ trả về những phần tử nào mà đánh giá là đúng. Ý tôi là, nó đã được nêu rõ trong tên -- một "bộ lọc".
Hãy xem một số ví dụ
Danh sách (iterable
) sau đây là điểm số của 10 học sinh trong một kỳ thi hóa học. Hãy lọc ra những người đã vượt qua với điểm số hơn 75...sử dụng filter
.
# Python 3
scores = [66, 90, 68, 59, 76, 60, 88, 74, 81, 65]
def is_A_student(score):
return score > 75
over_75 = list(filter(is_A_student, scores))
print(over_75)
Ví dụ tiếp theo sẽ là một trình phát hiện palindrome. Một "palindrome" là một từ, cụm từ, hoặc chuỗi đọc ngược lại giống như khi đọc xuôi. Hãy lọc ra những từ là palindrome từ một tuple (iterable
) của các từ nghi ngờ là palindrome.
# Python 3
dromes = ("demigod", "rewire", "madam", "freer", "anutforajaroftuna", "kiosk")
palindromes = list(filter(lambda word: word == word[::-1], dromes))
print(palindromes)
Mà sẽ xuất ra ['madam', 'anutforajaroftuna']
.
Khá gọn gàng đúng không? Cuối cùng, reduce()
Reduce
reduce
áp dụng một hàm có hai đối số một cách tích lũy cho các phần tử của một iterable, tùy chọn bắt đầu với một đối số đầu tiên. Nó có cú pháp như sau:
reduce(func, iterable[, initial])
Trong đó func
là hàm mà mỗi phần tử trong iterable
sẽ được áp dụng để tính toán tích lũy và initial
là giá trị tùy chọn mà được đặt trước các phần tử của iterable trong phép tính, và đóng vai trò là mặc định khi iterable trống. Những điều sau đây nên được lưu ý về reduce()
:
1. func
yêu cầu hai đối số, đối số đầu tiên trong số đó là phần tử đầu tiên trong iterable
(nếu initial
không được cung cấp) và phần tử thứ hai trong iterable
. Nếu initial
được cung cấp, thì nó trở thành đối số đầu tiên cho func
và phần tử đầu tiên trong iterable
trở thành phần tử thứ hai.
2. reduce
"giảm" (tôi biết, xin thứ lỗi) iterable
thành một giá trị đơn lẻ.
Như thường lệ, hãy xem một số ví dụ.
Hãy tạo phiên bản của riêng chúng ta của hàm tích hợp sẵn sum()
trong Python. Hàm sum()
trả về tổng của tất cả các phần tử trong iterable đã truyền vào nó.
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers)
print(result)
Kết quả, như bạn mong đợi, là 68
.
Vậy, điều gì đã xảy ra?
Như thường lệ, tất cả đều thuộc về các lần lặp: reduce
lấy phần tử đầu tiên và phần tử thứ hai trong numbers
và truyền chúng lần lượt vào custom_sum
. custom_sum
tính toán tổng của chúng và trả về cho reduce
. reduce
sau đó lấy kết quả đó và sử dụng nó như là phần tử đầu tiên cho custom_sum
và lấy phần tử tiếp theo (thứ ba) trong numbers
làm phần tử thứ hai cho custom_sum
. Nó làm điều này liên tục (tích lũy) cho đến khi numbers
hết phần tử.
Hãy xem điều gì xảy ra khi tôi sử dụng giá trị tùy chọn initial
.
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers, 10)
print(result)
Kết quả như bạn mong đợi, là 78
vì reduce
, ban đầu, sử dụng 10
như là đối số đầu tiên cho custom_sum
.
Đó là tất cả về Map, Reduce, và Filter trong Python. Thử các bài tập dưới đây để giúp xác định sự hiểu biết của bạn về mỗi hàm.
Exercise
Trong bài tập này, bạn sẽ sử dụng mỗi trong số map
, filter
, và reduce
để khắc phục mã bị lỗi.
from functools import reduce
# Use map to print the square of each numbers rounded
# to three decimal places
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
# Use filter to print only the names that are less than
# or equal to seven letters
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
# Use reduce to print the product of these numbers
my_numbers = [4, 6, 9, 23, 5]
# Fix all three respectively.
map_result = list(map(lambda x: x, my_floats))
filter_result = list(filter(lambda name: name, my_names, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers, 0)
print(map_result)
print(filter_result)
print(reduce_result)
#### Map
from functools import reduce
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
my_numbers = [4, 6, 9, 23, 5]
map_result = list(map(lambda x: round(x ** 2, 3), my_floats))
filter_result = list(filter(lambda name: len(name) <= 7, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers)
print(map_result)
print(filter_result)
print(reduce_result)
test_output_contains("[18.922, 37.088, 10.562, 95.453, 4.666, 78.854, 21.068]")
test_output_contains("['olumide', 'josiah', 'omoseun']")
test_output_contains("24840")
success_msg("Congrats! Nice work.")
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join over a million other learners and get started learning Python for data science today!