3 function bạn cần phải biết khi dùng Python

2 tháng 2, 2021 By DEVERA ACADEMY

Đối với một ngôn ngữ lập trình hướng đối tượng như Python, các hàm như map (), filter () và reduce() đem lại tính thực tế và hữu ích rất lớn. Chúng vừa có thể sử dụng riêng lẻ vừa có thể kết hợp để mang lại nhiều tiện ích cho việc lập trình. Bài viết này sẽ hướng dẫn bạn cách dùng 3 loại hàm quan trọng trong Python.


Nhưng trước khi đến với các ví dụ về công dụng và cách sử dụng của 3 hàm kể trên, chúng ta cần hiểu được Lambda là gì và làm sao để sử dụng nó.

Biểu thức Lambda

Biểu thức Lambda được sử dụng để tạo các hàm ẩn danh (một hàm không nhất thiết phải đặt tên). Lambda thường dùng cho các hàm sử dụng một lần và có thể viết gọn trong một dòng. Vì thế, nó rất hữu ích trong các hàm mà hàm đó lấy kết quả một hàm khác làm đối số.

Các hàm lambda có thể có một hoặc nhiều tham số nhưng chỉ có thể có một biểu thức. Cú pháp:

lambda <các tham số>: <Biểu thức>


Biểu thức Lambda với một tham số

Ví dụ: Hãy tạo một biểu thức lambda trả về bình phương của một số:


 

Thế là xong! Đầu tiên chúng ta bắt đầu với từ khóa lambda, sau đó là tham số num, dấu hai chấm và biểu thức bạn muốn hàm đó trả về: num ** 2.

Hàm lambda không cần từ khóa return. Và vì nó là hàm ẩn danh nên chúng ta không thể gọi lại. Nhưng khi kết hợp với việc sử dụng toán tử gán, chúng ta vẫn có thể gọi lại việc thực hiện hàm thông qua một biến:


 

Chúng ta có thể gọi thực thi hàm này giống như cách gọi các hàm được định nghĩa với từ khóa def:


 

Biểu thức Lambda với nhiều tham số

Ví dụ: Viết một hàm lambda trả về tổng của hai số.


Nếu chúng ta muốn biểu thức lambda có nhiều tham số, chúng ta chỉ cần tách các tham số đó bằng dấu phẩy.


Câu lệnh điều kiện trong biểu thức Lambda

Chúng ta có thể dùng các câu lệnh if-else trong biểu thức lambda, chỉ cần đảm bảo rằng tất cả đều nằm trên cùng một dòng.

Ví dụ: Tạo một hàm nhận vào hai số và trả về số lớn hơn.



Biểu thức lambda nhận vào hai số num1 và num2, trả về num1 nếu num1 lớn hơn num2 và ngược lại. Trong trường hợp 2 số bằng nhau, nó sẽ trả về num2. 


Tìm hiểu thêm về Lambda: https://towardsdatascience.com/lambda-expressions-in-python-9ad476c75438


Bây giờ, chúng ta hãy cùng bắt đầu với hàm đầu tiên: map


1. Map Function

Map cho phép chúng ta áp dụng một hàm cho mỗi phần tử trong một iterable object (ví dụ: list, string, tuple,...).Vì vậy, hàm map có hai đối số: hàm chúng ta muốn áp dụng và iterable object.

map(function, iterable)


Ví dụ: Cho một danh sách các số. Hãy tạo một danh sách mới chứa bình phương của các số đó. 



Map sẽ trả về một map object là một iterable object. Nếu chúng ta muốn tạo một list từ một iterable object, chúng ta cần phải chuyển map object của mình vào hàm list() - hàm này  được tích hợp sẵn.


Dòng code này hoạt động như thế nào?


 

Map lấy phần tử đầu tiên từ num_list là 1 và chuyển nó vào làm đối số cho hàm lambda (vì chúng ta đã truyền hàm đó vào làm đối số đầu tiên cho hàm map). Hàm lambda nhận đối số là 1 và trả về 1 bình phương, đối số đó được thêm vào map object. 

Sau đó, hàm map đã lấy phần tử thứ hai từ num_list là 2, chuyển nó vào làm đối số cho hàm lambda. Hàm lambda trả về bình phương của 2 là 4, sau đó thêm vào map object. 

Sau khi hoàn tất, việc chuyển những phần còn lại, hàm list chuyển map object vừa được tạo vào một list và list đó được gán vào biến squared_list.


Mã hóa: Caesar Cipher

Mật mã caesar mã hóa một tin nhắn bằng cách lấy từng chữ cái trong tin nhắn và thay thế nó bằng một chữ cái đã được dịch chuyển một khoảng cụ thể trong bảng chữ cái. Vì vậy, nếu chúng ta chọn khoảng cách là 1, thì mỗi ký tự sẽ được thay thế bằng ký tự cách nó một khoảng 1 đơn vị. Ví dụ như: chữ a sẽ được thay thế bằng chữ b, chữ b sẽ được thay thế bằng chữ c, ... Nếu chúng ta chọn khoảng cách là 2, thì a sẽ được thay thế bằng c, và b sẽ được thay thế bằng d.

Trong khi đếm, nếu đếm đến cuối bảng chữ cái thì chúng ta quay trở lại từ  đầu: chữ z sẽ được thay thế bằng chữ a (nếu chúng ta dịch chuyển 1 đơn vị), hoặc bằng chữ b (nếu chúng ta dịch chuyển 2 đơn vị).

Ví dụ: nếu tin nhắn chúng ta muốn mã hóa là ‘abc’ và chúng ta chọn khoảng cách là 1, thì tin nhắn được mã hóa sẽ là ‘bcd’. Nếu tin nhắn là ‘xyz’ thì tin được mã hóa sẽ là ‘yza’.


Áp dụng hàm map() để thực hiện

Trong trường hợp này, iterable object là một chuỗi và chúng ta muốn thay thế mỗi chữ cái trong chuỗi của bằng một chuỗi khác. Và map có thể làm chính xác điều đó!

Giả sử rằng đoạn tin sẽ chỉ gồm chữ thường. Và khoảng cách sẽ là một số trong khoảng từ 0–26. Vì ở đây, chúng ta chỉ thay thế các chữ cái bằng các chữ cái khác nên bất kỳ phần tử không phải chữ cái(dấu cách hoặc ký hiệu), sẽ không thay đổi.

Đầu tiên chúng ta có bảng chữ cái thường. Chúng ta có thể tự viết ra một chuỗi với tất cả các chữ cái thường hoặc chúng ta có thể sử dụng string module như sau:



Chúng ta có thể viết hàm mã hóa của mình như sau:



Chúng ta tạo hàm mã hóa với hai tham số: tin nhắn muốn mã hóa msg và khoảng cách n mà chúng ta muốn dịch chuyển. Tham số của map sẽ là một hàm lambda, hàm này lấy từng phần tử từ chuỗi tin nhắn và nếu phần tử là một chữ cái trong bảng chữ cái, nó sẽ được thay thế bởi chữ cái sau khi dịch chuyển... bằng cách lấy index hiện tại của chữ cái đó trong bảng chữ cái  abc.index (x), cộng n vào giá trị đó, rồi chia lấy dư tổng đó cho 26. Phép tính này sử dụng để bắt đầu trở lại đầu bảng chữ cái nếu chúng ta đã đến cuối (nếu abc.index (x) + n là một số lớn hơn 25). Nói cách khác, nếu ký tự ban đầu là z (sẽ có index là 25 trong chuỗi abc) và giá trị n là 2 thì (abc.index (x) + n)% 26 sẽ là 27% 26, có phần dư là 1. Do đó, thay thế chữ z bằng chữ cái có index là 1 trong bảng chữ cái là chữ b.


 

Hàm map sẽ trả về một map object. Do đó, chúng ta có thể sử dụng phương thức join để nối nó thành một chuỗi (chuyển từ iterator của map object). Sau đó, trả về chuỗi. Để tiến hành giải mã, chúng ta có thể sử dụng hàm giải mã sau (Lưu ý: chúng ta lấy abc.index (x) trừ n thay vì cộng):



2. Filter Function

Hàm Filter dùng để “lọc” một iterable object dựa trên điều kiện cụ thể. Điều kiện chính là hàm mà chúng ta truyền vào.

>>> filter (function, iterator)

Hàm filter nhận hai đối số là: function - hàm để kiểm tra điều kiện cụ thể và iterator - iterable object mà chúng ta muốn áp dụng. 

Hàm filter sẽ lấy từng phần tử từ iterator và đưa nó vào function đã được cung cấp. Nếu function trả về giá trị True, thì filter sẽ thêm giá trị đó vào filter object (có thể tạo một list như cách đã làm với map object ở trên). Nếu hàm trả về False, thì phần tử đó sẽ không được thêm vào filter object. 

Tóm lại, chúng ta có thể xem filter function là một hàm dùng để lọc iterable object dựa trên các điều kiện. Ví dụ: Cho một danh sách các số và tạo một danh sách mới chỉ chứa các số chẵn.



Phân tích những gì đã xảy ra trong dòng code dưới đây:



Hàm filter lấy phần tử đầu tiên từ num_list là 1, truyền nó vào làm đối số cho hàm lambda. Vì 1 không phải là số chẵn nên hàm lambda trả về False và 1 không được thêm vào filter object. Tiếp theo, lấy phần tử thứ hai từ num_list là 2 và truyền nó vào làm đối số cho hàm lambda. Hàm lambda trả về True, vì 2 là số chẵn, do đó 2 được thêm vào filter object. Sau khi đã duyệt xong hết num_list, hàm list sẽ chuyển filter object thành kiểu dữ liệu list và gán vào biến list_of_even_nums.


List Comprehensions so với Map và Filter

List Comprehension được sử dụng để tạo một list từ một tập hợp khác, có thể bằng cách áp dụng toán tử cho các phần tử, bằng cách lọc các phần tử theo một điều kiện cụ thể hoặc có thể kết hợp cả hai. Các thao tác được áp dụng trong list comprehensive tương ứng tương tự với map và filter. Ngoài ra, biểu thức bắt đầu của list comprehensive cũng tương tự như lambda được sử dụng bên trong map và filter.


Ví dụ:

List Comprehensive với mục đích tạo một list  bình phương các số chẵn trong khoảng từ 0 đến 9


Có thể dùng map, filter và lambda cho ra kết quả tương tự:



3. Reduce Function

Hàm Reduce sử dụng cho một iterable object và chúng ta chỉ thu được một giá trị tích lũy cộng dồn duy nhất. Hàm reduce nhận ba đối số. Hai đối số bắt buộc là: hàm (bản thân nó nhận hai đối số) và một iterable. Đối số không bắt buộc là initializer.

>>> reduce(function, iterable, [ initializer])

Vì reduce không phải hàm built-in nên khi sử dụng hàm reduce cần:


Đối số đầu tiên - function bản thân nó phải nhận hai tham số. Reduce sẽ sử dụng function để tiến hành tích lũy dồn 2 phần tử của iterable (từ trái sang phải).


Ví dụ:

Cho iterable là num_list như sau:


 

Chúng ta có thể thực hiện phép nhân dồn các số trong num_list như sau:



Reduce sẽ lấy từ 2 phần tử đầu tiên truyền vào hàm lambda, nhân dồn nó và trả về tích của (1*2 = 2). Ở những lần sau reduce chỉ lấy 1 phần tử tiếp theo là 3 và thực hiện nhân dồn theo hàm lambda với kết quả trước đó (2*3=6). Cứ tiếp tục cho đến hết num_list.

Giải thích một cách đơn giản như sau:

((((1*2)*3)*4)*5)

Về mặt toán học, nó tương tự:

f (f (f (f (f (x, y), y), y), y), y)

Lần đầu tiên hàm lambda nhận x và y (hai phần tử đầu tiên trong danh sách) và trả về một kết quả. Kết quả sử dụng làm x cho f (x, y) sau đó và y sẽ là phần tử tiếp theo trong danh sách, do đó f (f (x, y), y)...


Đối số thứ ba: Initializer

Đối số thứ ba là tùy chọn. Giá trị mặc định là None. Nếu chúng ta truyền vào một Initializer, nó sẽ được sử dụng làm giá trị x đầu tiên (thay vì phần tử đầu tiên của iterable). 

Nếu chúng ta truyền số 2 vào initializer trong ví dụ ở trên thì:


Khi đó đối số đầu tiên của x và y sẽ lần lượt là 2 và 1. Các bước còn lại tương tự. Tóm lại, initializer sẽ được tính toán đầu tiên trước các phần tử bên trong iterable.


Ví dụ sử dụng hàm reduce

Tìm tổng của các số trong list:


Tìm số lớn nhất trong list:


Tìm số bé nhất trong list:


Và còn rất nhiều ứng dụng khác nữa!


Lưu ý: Python có các hàm tích hợp sẵn như max(), min() và sum() dễ sử dụng hơn cho ba ví dụ kể trên. Tuy nhiên, mục tiêu bài này đó là chỉ ra cách dùng Reduce () để hoàn thành nhiều tác vụ khác nhau.


Có thể kết hợp reduce với filter hoặc map. Ví dụ: nếu chúng ta chỉ muốn trả về tích của các số lẻ, chúng ta có thể làm như vậy bằng cách kết hợp reduce và filter như sau:


Ở ví dụ này, iterable mà chúng ta truyền vào reduce là một filter object chỉ gồm các số lẻ. Sau đó, reduce sẽ trả về tích của các số đó.


Kết luận

Trong bài hướng dẫn này, chúng ta biết được cách tạo các hàm lambda. Tiếp theo là tìm hiểu về map và cách áp dụng một hàm cho từng phần tử bên trong một iterable, filter và cách sử dụng nó để lọc một iterable dựa trên điều kiện cụ thể. Ngoài ra, bài viết này đã so sánh map, filter với list comprehensive. Cuối cùng là hàm reduce và một vài ví dụ tham khảo về sự kết hợp các chức năng này.


Tác giả: Luay Matalka

Dịch bởi: Devera Academy