φυβλαςのβλογ
บล็อกของ phyblas



ภาษา python เบื้องต้น บทที่ ๒๑: คำสั่งพิเศษบางตัวที่เกี่ยวข้องกับฟังก์ชัน
เขียนเมื่อ 2016/03/06 10:25
แก้ไขล่าสุด 2021/09/28 16:42
ในบทนี้จะพูดถึงคำสั่งบางอย่างที่เกี่ยวข้องกับฟังก์ชัน ซึ่งความจริงแล้วอาจไม่ได้มีความจำเป็นต้องใช้มากนัก เพียงแต่ในบางกรณีก็ทำให้การเขียนโค้ดดูสั้นกระชับเรียบง่ายขึ้นจึงเหมาะใน บางกรณี และต่อให้บางคนไม่ได้กะจะใช้แต่ก็อาจเรียนรู้ไว้เผื่อไปศึกษาโค้ดของคนอื่น ได้

คำสั่งเหล่านั้นได้แก่ 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



<< บทที่แล้ว      บทถัดไป >>
หน้าสารบัญ


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
maya
javascript
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文