Data Warehouse

คำสั่งพิเศษที่เกี่ยวข้องกับฟังก์ชัน

ในบทนี้จะพูดถึงคำสั่งบางอย่างที่เกี่ยวข้องกับฟังก์ชัน ซึ่งความจริงแล้ว อาจไม่ได้มีความจำเป็นต้องใช้มากนัก เพียงแต่ในบางกรณี ก็ทำให้การเขียนโค้ดดูสั้นกระชับเรียบง่ายขึ้น จึงเหมาะใน บางกรณี และต่อให้บางคนไม่ได้กะจะใช้แต่ก็อาจเรียนรู้ไว้เผื่อไปศึกษาโค้ดของคนอื่น ได้ คำสั่งเหล่านั้นได้แก่ lambda, map, filter, any และ all

lambda

นอกจากการนิยามฟังก์ชันแบบทั่วไปที่ใช้กันแล้ว สำหรับฟังก์ชันที่แค่ต้องการให้มีการคืนค่า โดยที่ไม่ได้มีการดำเนินการอะไรอาจสามารถนิยามได้โดยใช้คำสั่ง lambda ลองยกตัวอย่างฟังก์ชันคำนวณง่ายๆ

def  f(x):  
return  x**3+4*x**2+5**x+1

หากนิยามโดยใช้ lambda ก็จะเป็น

f =  lambda  x:x**3+4*x**2+5**x+1

รูปแบบการเขียนอาจดูเข้าใจยากสักหน่อย แต่จะเห็นว่าดูแล้วเขียนสั้นลงเล็กน้อย
สรุปรูปแบบการเขียนคือ

<ชื่อตัวแปรที่ต้องการให้เป็นฟังก์ชัน> = lambda <อาร์กิวเมนต์>:<ค่าคืนกลับ>

ผลที่ได้ก็จะมีค่าเท่ากับการใช้ def ลองเทียบกันดู

def  <ชื่อตัวแปรที่ต้องการให้เป็นฟังก์ชัน>(<อาร์กิวเมนต์>):  
return  <ค่าคืนกลับ>

หากมีอาร์กิวเมนต์หลายตัวก็ใช้จุลภาคคั่นเช่นเดียวกับการนิยามฟังก์ชันทั่วไป เช่น

f =  lambda  x,y,z:x+y+z  
print(f(3,4,5))  # ได้ 12

จะได้ฟังก์ชันสำหรับบวกค่าตัวเลข ๓ ตัว

ข้อด้อยของการใช้ lambda ก็คือไม่สามารถสร้างฟังก์ชันสำหรับดำเนินการอะไรได้ ทำได้แต่ฟังก์ชันสำหรับคืนค่า แต่ข้อดีคือถ้าต้องการแค่ประกาศฟังก์ชันที่มีแค่การคืนค่าแล้วละก็ จะเขียนสั้นกว่าใช้ def สามารถสร้างฟังก์ชันพร้อมกันหลายๆตัวได้อย่างรวดเร็ว เช่นตัวอย่างนี้ สร้างฟังก์ชันบวกลบคูณหารพร้อมกัน

f4 = [lambda  x,y:x+y,lambda  x,y:x-y,lambda  x,y:x*y,lambda  x,y:x/y]  
print(f4[0](4,5))  # 9  
print(f4[1](4,5))  # -1  
print(f4[2](4,5))  # 20  
print(f4[3](4,5))  # 0.8

การทำแบบนี้ถ้าใช้ def ก็ต้องประกาศฟังก์ชันทีละตัว แล้วค่อยเอาชื่อฟังก์ชันมาเก็บรวมกันในลิสต์ ไม่สามารถทำทุกอย่างในบรรทัดเดียวได้แบบที่ใช้ lambda นอกจากนี้ lambda ยังมีข้อดีตรงที่สามารถสร้างแล้วใช้งานได้ทันทีโดยที่อาจไม่ต้องตั้งชื่อ ฟังก์ชัน (ไม่ต้องสร้างตัวแปรออบเจ็กต์ฟังก์ชัน) ซึ่งทำให้สะดวก ใช้งานได้รวดเร็ว เช่น

print((lambda  x:x**2)(10))  # ได้ 100  
print([(lambda  x:x+10)(x)  for  x in  range(10,20)])  # ได้ [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

ด้านหลังโคลอน : จะต้องเป็นค่าอะไรสักอย่างที่ต้องการให้คืนกลับ แต่จะใส่เป็นฟังก์ชันที่สั่งให้ทำงานอะไรบางอย่างก็ได้ แต่ในกรณีแบบนั้นหากฟังก์ชันนั้นไม่ได้ส่งค่าคืนกลับมาก็จะได้ None

print((lambda  x:print(x*2))(7))  # จะมีเลข 14 ถูก print ออกมาก่อน จากนั้นจะได้ None

ดังนั้นเราอาจใช้สร้างฟังก์ชันธรรมดาที่ไม่มีการคืนค่าก็ได้ เช่นฟังก์ชันที่จะพิมพ์เลข ๒ เท่าของที่ใส่เข้าไป

printx2 =  lambda  x:print(x*2)  
printx2(12)  # ได้ 24

หลัง lambda อาจไม่จำเป็นจะต้องใส่ตัวแปรเลยก็ได้ กรณีแบบนี้ก็จะได้ฟังก์ชันที่ไม่ต้องการอาร์กิวเมนต์

print((lambda:1)())  # ได้ 1

map

บางครั้งมีลิสต์อยู่อันหนึ่ง และมีฟังก์ชันอยู่อันหนึ่งซึ่งต้องการจะให้ใช้สมาชิกในลิสต์นั้นเพื่อเป็น อาร์กิวเมนต์ของฟังก์ชันแล้วคืนค่าออกมาโดยแยกกัน โดยทั่วไปก็จะต้องใช้ for เพื่อวนทำซ้ำให้ครบทุกตัว

def  f(x):  
return  x**3  
xxxx = [2,7,11,16]  
x3 = []  
for  x  in  xxxx:  
x3 += [f(x)]  
print(x3)  # ได้ [8, 343, 1331, 4096]

แต่ว่ามีวิธีที่จะเขียนให้ง่ายและสั้นขึ้นมาก คือการใช้คำสั่ง map

def  f(x):  
return  x**3  
xxxx = [2,7,11,16]  
x3 =  list(map(f,xxxx))  
print(x3)  # ได้ [8, 343, 1331, 4096]

map นั้นเป็นคำสั่งสำหรับให้คืนค่าที่ฟังก์ชันหนึ่งทำกับกลุ่มข้อมูลหนึ่ง การใช้ map นั้นเป็นการสร้างออบเจ็กต์ชนิดหนึ่งคือชนิด map ซึ่งเป็นอิเทอเรเตอร์ตัวหนึ่ง ถ้าต้องการให้เป็นลิสต์ก็ต้องคร่อมด้วย list() ไปอีกทีดังตัวอย่าง

ออบเจ็กต์ map สามารถทำงานได้เหมือนกับเป็นลิสต์เมื่อใช้คู่กับ for ดังนั้นในกรณีที่จะใช้กับ for อยู่แล้วก็ไม่จำเป็นต้องแปลงเป็นลิสต์ก็ได้

สรุปโครงสร้างของการใช้ map

<ตัวแปรที่รับค่าออบเจ็กต์ map> = map(<ฟังก์ชัน>,<ลิสต์>)

หรือถ้าต้องการเปลี่ยนเป็นลิสต์ทันที

<ตัวแปรที่รับค่าออบเจ็กต์ map> = list(map(<ฟังก์ชัน>,<ลิสต์>))

อนึ่ง ที่จริงแล้ววิธีที่เขียนสั้นๆได้อีกวิธีคือใช้ for สร้างลิสต์

x3 = [f(x)  for  x  in  xxxx]

ผลที่ได้ก็เหมือนกัน จะใช้แบบไหนก็แล้วแต่กรณี แล้วแต่ความถนัด ออบเจ็กต์ map เป็นอิเทอเรเตอร์ ซึ่งมีข้อดีคือจะประหยัดหน่วยความจำกว่าลิสต์และทำงานเร็วกว่าในกรณีที่ไม่มีความ จำเป็นต้องใช้งานในรูปแบบลิสต์ บางครั้ง map ยังใช้คู่กับ lambda เพื่อจะได้ไม่ต้องนิยามฟังก์ชันขึ้นมาก่อน ทำให้การเขียนยิ่งสั้นกะทัดรัด

xxxx = [2,7,11,16]  
x3 =  list(map(lambda  x:x**3,xxxx))  
print(x3)  # ได้ [8, 343, 1331, 4096]

ลองประยุกต์ใช้กับอย่างอื่นอีก เช่น เปลี่ยนตัวเลขเป็นสายอักขระ

print(list(map(str,range(10))))  # ได้ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']  
print(''.join(map(str,range(10))))  # ได้ 0123456789

จะเห็นว่าเมธอด join สามารถใช้กับออบเจ็กต์ map ได้โดยตรงโดยไม่ต้องแปลงเป็นลิสต์ก่อน

map คืนค่าเป็นอิเทอเรเตอร์แบบนี้แค่ในไพธอน 3 เท่านั้น ส่วนในไพธอน 2 นั้น map จะคืนค่าเป็นลิสต์ไม่ใช่อิเทอเรเตอร์ ดังนั้นไม่ต้องมาแปลงเป็นลิสต์อีกทีเพื่อแสดงผล
filter ก็เช่นเดียวกัน

รายละเอียด

filter

บางครั้งเรามีลิสต์อยู่อันหนึ่ง แล้วต้องการคัดกรองส่วนประกอบบางอย่างออกไป โดยทั่วไปแล้วก็อาขใช้ for วนเพื่อตรวจแต่ละอันแล้วดูเงื่อนไขว่าตรงหรือเปล่าถ้าไม่ตรงก็คัดออก ตัวอย่าง มีลิสต์อยู่อันหนึ่งที่มีสายอักขระอยู่หลายตัว จะกรองเอาเฉพาะสายอักขระที่สั้นกว่า 6 ตัวอักษร

k = ['egao','kibou','yuuki','jishin','kagayaki','ai','yasashisa']  
f = []  
for  s  in  k:  
if(len(s)<6):  
f += [s]  
print(f)  # ได้ ['egao', 'kibou', 'yuuki', 'ai']

หรืออาจเขียนสั้นๆด้วยวิธีการสร้างลิสต์ใหม่จาก for เป็น

f = [s  for  s  in  k  if(len(s)<6)]  
print(f)  # ได้ ['egao', 'kibou', 'yuuki', 'ai']

แต่ก็มีอีกวิธีหนึ่งที่สามารถใช้ได้ คือใช้ filter ซึ่งมีวิธีการเขียนดังนี้

def  filt(s):  # นิยามฟังก์ชันสำหรับคัดกรองขึ้นมาก่อน  
return  len(s)<6  
f =  list(filter(filt,k))  
print(f)  # ได้ ['egao', 'kibou', 'yuuki', 'ai']

เขียนแบบนี้อาจดูเข้าใจยากสักหน่อย อาร์กิวเมนต์ตัวแรกของ filter คือฟังก์ชันซึ่งจะถูกพิจารณาค่าความจริงเท็จ ในที่นี้ชื่อ filt ส่วนตัวที่สองคือลิสต์ที่จะนำมาคัดกรอง คือลิสต์ k filter จะทำการกรองสมาชิกในลิสต์ที่แทนค่าลงในฟังก์ชันนั้นแล้วได้ค่าความจริงเป็น จริง เอาที่ให้ค่าเป็นเท็จออกไป ในที่นี้จะเป็นจริงเมื่อความยาวของสายอักขระน้อยกว่า 6 ผลที่ได้จะอยู่ในรูปของออบเจ็กต์ชนิด filter ซึ่งก็เป็นอิเทอเรเตอร์เช่นเดียวกับ map filter มักใช้คู่กับ lambda เพื่อจะทำให้ไม่จำเป็นต้องนิยามฟังก์ชันขึ้นมาก่อน ดูกะทัดรัดขึ้นมาก

f =  list(filter(lambda  s:len(s)<6,k))  
print(f)  # ได้ ['egao', 'kibou', 'yuuki', 'ai']

ตัวอย่างการใช้ การหาเลขจำนวนเฉพาะโดยวิธีการตะแกรงของเอราโตสเธเนส

ตะแกรงของเอราโตสเธเนส (Ἐρατοσθένης) มีที่มาจากนักคณิตศาสตร์กรีกโบราณ เป็นการคัดกรองหาเลขที่เป็นจำนวนเฉพาะโดยการไล่ตัดตัวเลขที่หารจำนวนเฉพาะ ลงตัวไปทีละนิด โดยเริ่มดูจาก 2 ตามด้วย 3 แล้วก็ 5 ไปเรื่อยๆ กล่าวคือ เริ่มไล่ดูว่าตัวไหนหาร 2 ลงตัวก็ตัดจำนวนนั้นออก จากนั้นทำซ้ำกับ 3 ส่วนเลข 4 ถูกตัดไปแล้วจึงข้ามไปทำ 5 แล้วก็ข้ามไป 7 และ 11 ต่อไปเรื่อยๆ

n = 121  # จำนวนตัวเลขที่จะพิจารณา  
ch =  range(2,n+1)  
i = 0  # ตำแหน่งของสมาชิกในลิสต์ที่จะใช้เป็นตัวกรอง เริ่มจากตัวแรก  
while(ch[i]<=n**0.5):  # ให้วนกรองไปเรื่อยๆจนกว่าจะถึงตัวเลขที่เท่ากับรากที่สองของ n  
ch =  list(filter(lambda  x:x%ch[i]!=0 or x==ch[i],ch))  # คัดกรองเอาไว้เฉพาะตัวที่หารตัวที่เป็นตัวกรองอยู่ไม่ลงตัว และตัวกรองเอง  
print('รอบที่ %d: '%(i+1)+','.join(map(str,ch)))  # แสดงผลเลขที่เหลืออยู่ในแต่ละรอบ  
i += 1  # พิจารณาตัวถัดไป

ผลลัพธ์

รอบที่ 1: 2,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,115,117,119,121  
รอบที่ 2: 2,3,5,7,11,13,17,19,23,25,29,31,35,37,41,43,47,49,53,55,59,61,65,67,71,73,77,79,83,85,89,91,95,97,101,103,107,109,113,115,119,121  
รอบที่ 3: 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,49,53,59,61,67,71,73,77,79,83,89,91,97,101,103,107,109,113,119,121  
รอบที่ 4: 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,121  
รอบที่ 5: 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113

any กับ all

any เป็นฟังก์ชันสำหรับตรวจสอบความจริงเท็จของข้อมูลกลุ่ม ถ้าในกลุ่มมีข้อมูลที่มีค่าความจริงเท็จเป็น True แม้แต่ตัวเดียวจะได้ค่า True ทันที any(ลิสต์) เทียบเท่ากับ (1 in ลิสต์)

print(1  in  [1,0,1])  # ได้ True  
print(any([1,0,1]))  # ได้ True  
print(any([0,0,0]))  # ได้ False

ส่วน all นั้นจะตรวจว่าข้อมูลในกลุ่มเป็นจริงทั้งหมดหรือเปล่า ถ้ามี False แม้แต่ตัวเดียวจะเป็น False ทันที all(ลิสต์) เทียบเท่ากับ (1 in ลิสต์)

print(0  not in  [1,0,1])  # ได้ False  
print(all([1,0,1]))  # ได้ False  
print(all([1,1,1]))  # ได้ True

การประยุกต์ใช้นั้นเช่นเดียวกับ filter คือสามารถใช้คู่กับ map และ lambda ได้ โดยใช้ lambda สร้างฟังก์ชันที่ตรวจสอบเงื่อนไขที่ต้องการ จากนั้นใช้ map เพื่อให้ฟังก์ชันนั้นทำกับทุกตัวในลิสต์ ผลที่ได้ก็คือจะได้ลิสต์ที่มีค่า True หรือ False (1 หรือ 0)

print(any(map(lambda  x:x>0,[-7,-1,2,6])))  # ได้ True  
print(all(map(lambda  x:x>0,(-5,0,3,9))))  # ได้ False  
print(all(map(lambda  x:x%2,range(-7,9,2))))  # ได้ True  
print(any(map(lambda  x:x%2==0,range(-5,13,4))))  # ได้ False

สรุปเนื้อหา
ทั้ง lambda, map และ filter นั้นลักษณะการเขียนอาจดูแล้วเข้าใจยากในช่วงแรกๆ แต่หากใช้เป็นแล้วในบางกรณีจะช่วยให้การเขียนง่ายขึ้นมาก
แต่ก็ไม่ใช่คำสั่งที่ขาดไม่ได้ ดังนั้นบางคนอาจไม่เคยต้องใช้มันเลย แต่ก็อาจควรเรียนรู้ไว้สักหน่อยเผื่อว่าไปอ่านโค้ดที่คนอื่นเขียนแล้วเขาใช้ จะได้เข้าใจได้

อ้างอิง
http://python.keicode.com/lang/functions-lambda.php
http://atkonn.blogspot.com/2008/02/python-python27-lambda.html
http://python.civic-apps.com/map-reduce-filter
http://python.civic-apps.com/list-comprehensions
http://diveintopython3-ja.rdy.jp/porting-code-to-python-3-with-2to3.html
http://jutememo.blogspot.com/2008/09/python-map-filter-reduce.html
http://cortyuming.hateblo.jp/entry/20080821/p1

Reference : https://phyblas.hinaboshi.com/tsuchinoko21