Python: Lambda function คือ อะไร ใช่โคมไฟไหม (เอ๊ย…นั่นมัน Lamptan) map, filter, reduce, zip

Lambda function คือ การประกาศฟังก์ชันเล็กๆ เราอาจเรียกว่า Anonymous function หรือ nameless function ก็ได้ (ที่เรียกแบบนี้เพราะว่า มันไม่ต้องประกาศชื่อ function) ประโยชน์ของมันคือ ง่าย กระชับ บางครั้งฟังก์ชันนั้นเราใช้แค่ครั้งเดียวไง ก็ขอเขียนสั้นๆไว้ดีกว่า และเหมาะกับการใช้ซ้อนกับฟังก์ชันอื่นได้ด้วย
Syntax:
lambda arguments : expression
Arguments จะรับเข้ามากี่ตัวก็ได้ เพียงแค่คั่นด้วย comma แบบทั่วๆไปเลยและ
การทำงานของ Lambda จะ return ผลลัพธ์จาก expression กลับมาเสมอ ลองไปดูตัวอย่างกันดีกว่า
นี่คือ function แบบปกติ
def rectangle(w, h):
return w * h
if __name__ == '__main__':
print(rectangle(10, 5))# ---------------------------
# output:
# 50
เมื่อแปลงเป็น lambda function จะเป็นแบบนี้
rect = lambda w, h: w * h if __name__ == '__main__':
print(rect(10, 5))# ---------------------------
# output:
# 50
# จะได้ผลลัพธ์เท่ากันนะ ส่วนวิธีการใช้...เพราะเป็นฟังก์ชันไม่มีชื่อเราจึง assign และเรียกผ่านตัวแปร rect แทน
# แบบนี้คือรับ argument 2 ตัวคือ w, h และเราไม่ต้องกำหนดส่วนของการ return เพราะจะ return กลับมาเสมอ
การเรียกใช้งาน lambda function จะเรียกได้ 2 ลักษณะ คือ
1. Assign และเรียกผ่านตัวแปรแบบด้านบนได้เลย (เพราะมันไม่มีชื่อ)
2. ใช้ในลักษณะโดยผ่านค่าตัว lambda ให้เป็นเสมือน parameter ตัวนึงเข้าไปใน function ลักษณะนี้
rect = lambda w, h: w * hdef area(func, w, h):
return func(w, h)if __name__ == '__main__':
print(area(rect, 5, 10))# ---------------------------
# output:
# 50
วิธีการนี้คือ เรียกผ่านฟังก์ชัน area(rect, 5, 10) เป็นการ pass function เข้าไปเสมือน parameter ตัวนึงซึ่งวิธีการแบบนี้ จะใส่ function อะไรเข้าไปก็ได้ในกรณีที่มี function อื่นที่ต้องใช้ w, h เหมือนกัน เช่น
triangle = lambda w, h: w * h * 0.5def area(func, w, h):
return func(w, h)if __name__ == '__main__':
print(area(triangle, 5, 10))#---------------------------
# output:
# 25.0
- ในการใช้ lambda แบบเท่ๆนี้ เราสามารถกำหนด default และ มีเงื่อนไขได้แบบนี้นะ
# แบบ function ปกติ
def get_value(attr: str, default: str = '0') -> str:
if isinstance(source, Document):
return str(getattr(source, attr, default))
return str(source.get(attr, default))
# แบบ lambda function
get_value = lambda attr, default='0': str(getattr(source, attr, default))
if isinstance(source, Document) else str(source.get(attr, default))
# เมื่อนำไปใช้งานก้อเรียก fucntionได้ปกติเลย
get_value('vat_amount')

Apply with map() and list():
- map() เป็นการทำงานกับสมาชิกทุกตัว เหมือนกับมี loop for อยู่ภายในนะคับ เราเพียงแค่กำหนด literable เท่านั้น ในรูปด้านล่างคือ usd

- จริงๆแล้วเราสามารถใช้ for-loop แทน map() ก็ได้เช่นกันนะ ในบางเคสเช่นแบบนี้คือ ต้องการนำ element ทุกตัวมา *2 ก็สามารถเขียนได้ 2 แบบเลย
values = [1, 2, 3, 4, 5]
new_values = [v*2 for v in values] # ใช้ for จะดู clean กว่านะ
map_values = list(map(lambda x: x*2, values)) # แต่ถ้าใช้ map จะต้องใช้ร่วมกับ list ด้วย (เพราะไม่งั้นผลลัพธ์จะเป็น object นะ)
print(new_values)
print(map_values)
---- outut ----
[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
ไหนๆก้อไหนๆล่ะ ถ้าเปรียบเทียบกันภาษา dart ลองดูเล่นๆนะ
วิธีการเขียนจะเปลี่ยนไปแบบนี้
Python:
thb = list(map(lambda v: v * 32, usd))Dart:
var thb = usd.map((v) => v * 32).toList();
map(), filter(), reduce(), zip()
นอกจากข้อดีที่สามารถเขียนได้สั้น แบบไม่ต้องมาประกาศ def และไม่ต้องเขียน return ด้วย เรายังสามารถนำไปประยุกต์ใชักับฟังก์ชันอื่นๆได้อีก
map(function, collection):
จำไว้ว่า map ให้เป็นอะไร (function, type) ด้วยอะไร (iterable) เช่น map ให้เป็น str ด้วย range
list(map(str, range(1, 5))) # map ให้เป็น str ด้วย [1,2,3,4]
# output
# ['1', '2', '3', '4']
หรือ
nums = [2, 4, 6]
result = map(lambda x: x**2, nums)
print(list(result))
# หรือจะใส่ list แบบนี้ก็ได้
result = list(map(lambda x: x**3, nums))
print(result)# -------------------------
# output
# [4, 16, 36]
# [8, 64, 216]
การใช้ map
มี syntax
คือ map(function, collection)
แต่บางครั้งถ้าเราต้องการให้ map ออกมาเป็น data type อะไรก็สามารถทำได้เช่นกันนะ จากรูปด้านล่าง
- แปลง
751
ให้เป็นstr(751)
เพื่อให้เป็นiterable
หรือ เข้าfor-loop
ได้ - ต้องการ
map
ให้เป็นint
ก็ใส่int
เข้าไปได้เลย ซึ่งรูปเต็มๆคือ แบบนี้นะ
list(map(lambda i: int(i), str(751))) # หรือ list(map(int, str(751)))
- สุดท้ายแปลง
return
ออกมาเป็นlist
ด้วยlist()

map
เพื่อให้แปลงเป็น int
นะ (จริงๆแล้ว int
ก็เป็น class
นึงที่เราเอามาใช้งานได้)นอกจากนี้มาดู used case
ของการใช้ map()
ร่วมกับ dict
ในการเช็คว่ามี key
นั้นๆไหมใน dict
โดยไม่สนใจ case sensitive
นะ

map
เพื่อเปลี่ยน key ใน dict ให้เป็น lower ก่อนที่จะนำไปเช็คใน ifอีกหนึ่ง used case
ของการใช้ map()
ร่วมกับ sum()
ในการรวมค่าที่ต้องการ

filter(function, iterable)
ตัวอย่างการใช้ filter
อยู่ด้านล่างสุดนะคับ เป็น used case
ที่เรามักจะเจอบ่อยๆ
nums = [1,2,3,4,5,6,7,8,9,10]
result = list(filter(lambda x: x >= 5, nums))
print(result)# -------------------------
# output
# [5, 6, 7, 8, 9, 10]
reduce(function, iterable)
สำหรับ reduce()
จะไม่ใช่ default common function
ใน Python3 นะคับ ถ้าจะใช้จะต้อง import
เข้ามาแบบนี้ก่อน
from functools import reduce
function นี้จะคืนค่าออกมาแค่ค่าเดียวนะ ไม่ได้คืนเป็น collection เหมือนกับอันอื่นนะ มักจะใช้ในการ calculate ผลลัพธ์จาก elements ทั้งหมด
(reduces a collectionให้เหลือแค่ single value)

การ sum
ด้วย reduce()

# เราอาจลดทอนให้เหลือแค่ lambda ก็จะง่ายขึ้น
from functools import reduce
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = reduce(lambda x, y: x + y, nums)
print(result)
# -------------------------
# output
# 55

จริงๆแล้ว reduce()
มีประโยชน์มากๆ จากการที่มันจับคู่ให้ไปเรื่อยๆจากหน้าไปหลัง เราสามารถนำมา apply สร้าง getattr()
ที่เข้าถึงได้หลายๆชั้นแบบนี้

zip(iterator1, iterator2, iterator3 …)
function
นี้เป็นการผูกข้อมูลที่มี index
เดียวกันเข้าด้วยกันนะ โดยจะผูกกี่ iterables
ก็ได้ จากนั้นจะ return
ออกมาเป็น iterable of tuples
, จากนั้นเราสามารถแปลงให้เป็น list
หรือ type
ที่เราต้องการได้เลย เช่น
questions = [
'7 x 1 = ',
'7 x 2 = ',
'7 x 3 = ',
'7 x 4 = ',
'7 x 5 ='
]
answers = ['7', '14', '21', '28', '35']
result = list(zip(questions, answers))
print(result)
# -------------------------
# output
# [('7 x 1 = ', '7'), ('7 x 2 = ', '14'), ('7 x 3 = ', '21'), ('7 x 4 = ', '28'), ('7 x 5 =', '35')]
ดูอีกตัวอย่างนะคับ
name = ["John", "Charles", "Mike"]
age = [35, 23, 16]
position = ['Programmer', 'Manager', 'Designer']
result = list(zip(name, age, position))
print(result)
# Output
# [('John', 35, 'Programmer'), ('Charles', 23, 'Manager'), ('Mike', 16, 'Designer')]
ลองมาดู used case
ในการสร้าง check digit
เพื่อสร้างข้อมูล Barcode
ตามสูตรที่ได้รับมาครับ

def calculate_check_digit(student_code: str, ref2: str, amounts: str) -> str:
weight_list = [7, 5, 1] * 10
weight_r1 = weight_list[:11] # First 11 digits for ref1
weight_r2 = weight_list[11:21] # Since position 12-21 for ref2
weight_amount = weight_list[21:] # Since 22 onwards for amount
r1_list = [int(r1) for r1 in student_code]
r2_list = [int(r2) for r2 in ref2]
amount_list = [int(amount) for amount in amounts]
sum1 = sum(w * d for w, d in zip(weight_r1, r1_list))
sum2 = sum(w * d for w, d in zip(weight_r2, r2_list))
sum3 = sum(w * d for w, d in zip(weight_amount, amount_list))
check_digit = str((sum([sum1, sum2, sum3]) * 2) % 100)
return check_digit.zfill(2) if len(check_digit) < 2 else check_digit
check_digit = calculate_check_digit(
student_code='55050427733',
ref2='1234567890',
amounts='15000'
)
print(check_digit)
# output
# 80
สำหรับ zip()
ไม่ได้ใช้ lambda function นะคับ แค่ review เฉยๆ
Used cases ในการใช้ Lambda function ใน Django project:
1. การ Filter dict in list
เช่น ถ้าเรามี leave_dates ออกมาแบบนี้
สิ่งที่ผมต้องการคือ ต้องหาว่าใน list ของ leave_dates เนี่ย
- มี value ของ approve
ที่เป็น True อยู่ทั้งหมดกี่วัน
- และต้องการกรองออกมาเป็น list ของ objects นั้นๆเลย

จากรูปนะคับ คือจะหา len
และ list ของ objects ที่ approve มีค่าเป็น True

ในรูปด้านล่างจะเหลือแค่บรรทัดเดียวนะคับ คือ จะใช้ filter() เข้ามาช่วย โดย filter(function, iterable) ซึ่ง function ด้านในจะเป็น lambda function และ iterable จะเป็น leave_dates ที่เป็น list นั่นเอง
จากนั้นเมื่อ filter ออกมาได้แล้ว ก็แปลงให้เป็น list อีกครั้งนึง (ไม่งั้นมันจะด่านะ ว่าผลของการ filter จะต้องถูกใช้ด้วย)

2. การ filter โดยแยก function ออกมาเขียนภายนอก (ไม่ได้ใช้ lambda)
# ให้เอาเฉพาะตัวเลขออกมานะ ทั้งที่เป็น String และ number
data = ['1', 2, 'Love', '5.22', 'one', 8.55, '130030', ' 554.3']
def is_numeric(item):
if isinstance(item, str):
try:
float(item)
return True
except ValueError:
return False
elif isinstance(item, (int, float)):
return True
else:
return False
numeric_data = list(filter(is_numeric, data))
print(numeric_data)
# ==========================================
# Output
# ['1', 2, '5.22', 8.55, '130030', ' 554.3']
จะเห็นว่ามีทั้งตัวเลขที่เป็น String, int, float
เลยนะ ฉะนั้นเรามาลองใช้ filter()
อีกแบบที่ต้องเขียน function
ไว้ภายนอก เพราะเคสนี้จะซับซ้อนขึ้น

3. Condition in short for-loop
จริงๆอันนี้ไม่เกี่ยวกับ lambda function นะคับ แต่ผมเห็นว่าเท่ดี ด้วยวิธีการเขียนแบบใส่เงื่อนไขใน short for-loop ได้เลย จึงมาเพิ่มเติมตรงนี้เลยคับ
อันนี้คือ short for-loop แบบที่ยังไม่มีเงื่อนไขด้านในนะ


อันนี้จะเป็น condition ที่ยากขึ้นมาหน่อยคือ เป็นการเช็คว่า list_of_deleted_id
มันมีอยู่ใน leave_type_settings_from_db
รึป่าว
ถ้ามี id นั้นๆอยู่ ก็ให้ return ออกมาเป็น name แทนนะ (ไม่ใช่ id) แต่ถ้าไม่มีก็ต้องไม่พังนะ งงมะ อิอิ

References:
https://www.youtube.com/watch?v=qUlI5vX6aNo
https://www.youtube.com/watch?v=pgQqvRcKf24