ข้ามไปยังเนื้อหาหลัก
Editor

map(), filter(), reduce() และฟังก์ชันอันดับสูงใน Python

ปานกลาง25 นาที7 แบบฝึกหัด100 XP

บทเรียนนี้แปลจากต้นฉบับภาษาอังกฤษRead in EnglishLeer en españolЧитать на русскомLer em portuguêsแจ้งข้อผิดพลาดในการแปล

0/7 แบบฝึกหัด

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

Python มีฟังก์ชันสามตัวที่ทำงานเหมือนสายการผลิตนี้ทุกประการ: map() แปลงข้อมูลทุกตัว filter() เก็บไว้เฉพาะข้อมูลที่ต้องการ และ reduce() รวมทุกอย่างให้เหลือผลลัพธ์เดียว เมื่อใช้ร่วมกัน ฟังก์ชันเหล่านี้ช่วยให้ประมวลผลกลุ่มข้อมูลได้อย่างเป็นระเบียบและอ่านง่าย

ในบทเรียนนี้ คุณจะได้เรียนรู้ว่าเครื่องมือเหล่านี้ทำงานอย่างไร ควรใช้เมื่อไร และเมื่อไรที่ list comprehension (การสร้างลิสต์แบบย่อ) อาจเป็นทางเลือกที่ดีกว่า เมื่อจบบทเรียน คุณจะเขียนไปป์ไลน์ (pipeline) ประมวลผลข้อมูลได้ดีจนหัวหน้าคนงานในโรงงานยังต้องภูมิใจ

ฟังก์ชันอันดับสูงคืออะไร

ฟังก์ชันอันดับสูง (higher-order function) คือฟังก์ชันที่รับฟังก์ชันอื่นเป็นอาร์กิวเมนต์ หรือคืนค่าออกมาเป็นฟังก์ชัน แค่นั้นเอง

คุณเคยใช้มาแล้วโดยไม่รู้ตัว ฟังก์ชันในตัว sorted() มีพารามิเตอร์ key ที่ให้ส่งฟังก์ชันเข้าไป:

sorted() เป็นฟังก์ชันอันดับสูง
words = ["banana", "apple", "cherry"]
# sorted() is a higher-order function
# It takes len as an argument - a function!
sorted_words = sorted(words, key=len)
print(sorted_words)
ผลลัพธ์
['apple', 'banana', 'cherry']

ในตัวอย่างนี้ len ถูกส่งเป็นอาร์กิวเมนต์ให้ sorted() ฟังก์ชัน sorted จะเรียก len กับข้อมูลแต่ละตัวเพื่อตัดสินลำดับ คุณไม่ได้เรียก len() เอง เพียงแค่บอก sorted ว่าจะใช้ฟังก์ชันไหน

map(), filter() และ reduce() ทำงานแบบนี้ทั้งหมด คุณส่งฟังก์ชันและกลุ่มข้อมูลให้ แล้วฟังก์ชันเหล่านี้จะนำฟังก์ชันที่ได้รับไปใช้กับข้อมูลแต่ละตัว นี่คือสิ่งที่ทำให้ทั้งสามเป็นฟังก์ชันอันดับสูง

map(): แปลงข้อมูลทุกตัว

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

รูปแบบคือ map(function, iterable) ซึ่งคืนค่าเป็นออบเจกต์ map ที่ปกติจะนำไปแปลงเป็นลิสต์

ใช้ map() คูณตัวเลขทุกตัวด้วยสอง
numbers = [1, 2, 3, 4, 5]
# Double every number
def double(x):
return x * 2
result = list(map(double, numbers))
print(result)
ผลลัพธ์
[2, 4, 6, 8, 10]

map(double, numbers) เรียก double(1), double(2), double(3) ไปเรื่อย ๆ และเก็บผลลัพธ์ทั้งหมดไว้ ส่วน list() ที่ครอบอยู่จะแปลงออบเจกต์ map เป็นลิสต์ธรรมดาเพื่อให้แสดงผลได้

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

map() กับ lambda และฟังก์ชันในตัว
numbers = [1, 2, 3, 4, 5]
# Same thing, but with a lambda
result = list(map(lambda x: x * 2, numbers))
print(result)
# More examples
names = ["alice", "bob", "charlie"]
print(list(map(str.upper, names)))
prices = [10.5, 20.3, 5.99]
print(list(map(round, prices)))
ผลลัพธ์
[2, 4, 6, 8, 10]
['ALICE', 'BOB', 'CHARLIE']
[10, 20, 6]

คุณส่งฟังก์ชันใดก็ได้ให้ map() ทั้งฟังก์ชันที่เขียนเอง lambda หรือฟังก์ชันในตัว เช่น str.upper, round, len หรือ int

filter(): เก็บไว้เฉพาะสิ่งที่ต้องการ

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

ใช้ filter() เก็บเฉพาะเลขคู่
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep only even numbers
def is_even(x):
return x % 2 == 0
result = list(filter(is_even, numbers))
print(result)
ผลลัพธ์
[2, 4, 6, 8, 10]

filter(is_even, numbers) เรียก is_even(1) ซึ่งคืนค่า False (1 จึงถูกตัดทิ้ง) is_even(2) คืนค่า True (2 จึงถูกเก็บไว้) ไปเรื่อย ๆ เฉพาะข้อมูลที่ผ่านการทดสอบเท่านั้นที่จะเหลืออยู่

ตัวอย่าง filter() เพิ่มเติม
words = ["hello", "", "world", "", "python", ""]
# Remove empty strings - filter with bool
result = list(filter(bool, words))
print(result)
# Keep words longer than 4 characters
long_words = list(filter(lambda w: len(w) > 4, words))
print(long_words)
# Keep positive numbers
numbers = [-3, -1, 0, 2, 5, -4, 8]
positives = list(filter(lambda x: x > 0, numbers))
print(positives)
ผลลัพธ์
['hello', 'world', 'python']
['hello', 'world', 'python']
[2, 5, 8]

การส่ง bool ให้ filter() เป็นเทคนิคที่เข้าท่า สตริงว่าง ศูนย์ None และลิสต์ว่าง ล้วนเป็นค่า "falsy" ใน Python คือ bool() คืนค่า False สำหรับค่าเหล่านี้ ดังนั้น filter(bool, items) จะตัดค่า falsy ทั้งหมดออก

ใช้ map() และ filter() ร่วมกัน

พลังที่แท้จริงมาจากการต่อฟังก์ชันเหล่านี้เป็นทอด ๆ ใช้ filter ก่อนเพื่อเลือกข้อมูล แล้วใช้ map เพื่อแปลงข้อมูล เหมือนสถานีต่าง ๆ ในสายการผลิต

ต่อ filter() กับ map() เป็นทอด
scores = [45, 82, 91, 67, 55, 38, 73, 88, 95, 60]
# Step 1: Filter passing scores (>= 60)
passing = filter(lambda s: s >= 60, scores)
# Step 2: Add 5 bonus points to each passing score
boosted = map(lambda s: s + 5, passing)
# Convert to list and print
result = list(boosted)
print(result)
ผลลัพธ์
[87, 96, 72, 78, 93, 100, 65]

เนื่องจากทั้ง filter() และ map() ทำงานแบบ lazy คุณจึงต่อกันได้โดยไม่ต้องสร้างลิสต์ระหว่างทาง ข้อมูลไหลผ่านไปป์ไลน์ได้อย่างมีประสิทธิภาพ

ต่อไปเป็นตัวอย่างที่ใกล้เคียงการใช้งานจริงมากขึ้น คือการประมวลผลลิสต์ราคาสินค้า:

ไปป์ไลน์ใช้งานจริง: กรองก่อนแล้วจึงแปลง
prices = [12.99, 5.50, 89.99, 3.25, 45.00, 2.99, 150.00]
# Keep prices over $10, then apply 20% discount
result = list(
map(
lambda p: round(p * 0.8, 2),
filter(lambda p: p > 10, prices)
)
)
print(result)
ผลลัพธ์
[10.39, 71.99, 36.0, 120.0]

reduce(): รวมข้อมูลทุกตัวให้เหลือค่าเดียว

reduce() รับฟังก์ชันและ iterable แล้วรวมข้อมูลทุกตัวให้เหลือค่าเดียว นี่คือสถานีสุดท้ายในสายการผลิต ที่ชิ้นส่วนทั้งหมดถูกประกอบเป็นผลิตภัณฑ์หนึ่งชิ้น

reduce() ไม่ใช่ฟังก์ชันในตัว ต่างจาก map() และ filter() คุณต้อง import มาจากโมดูล functools

ใช้ reduce() รวมค่า
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# Sum all numbers: ((((1+2)+3)+4)+5) = 15
total = reduce(lambda a, b: a + b, numbers)
print(f"Sum: {total}")
# Multiply all numbers: ((((1*2)*3)*4)*5) = 120
product = reduce(lambda a, b: a * b, numbers)
print(f"Product: {product}")
ผลลัพธ์
Sum: 15
Product: 120

ต่อไปนี้คือการทำงานของ reduce() ทีละขั้นสำหรับการหาผลรวม ฟังก์ชันรับอาร์กิวเมนต์สองตัว คือผลลัพธ์สะสมจนถึงตอนนี้ (a) และข้อมูลตัวถัดไป (b):

  • เริ่มจากข้อมูลสองตัวแรก: a=1, b=2 ได้ 3
  • นำผลลัพธ์นั้นกับข้อมูลตัวถัดไป: a=3, b=3 ได้ 6
  • ทำต่อไป: a=6, b=4 ได้ 10
  • สุดท้าย: a=10, b=5 ได้ 15
  • ตัวอย่าง reduce() เพิ่มเติม
    from functools import reduce
    # Find the longest word
    words = ["cat", "elephant", "dog", "hippopotamus"]
    longest = reduce(lambda a, b: a if len(a) >= len(b) else b, words)
    print(f"Longest word: {longest}")
    # Join words with a separator
    result = reduce(lambda a, b: f"{a} & {b}", ["Alice", "Bob", "Charlie"])
    print(result)
    ผลลัพธ์
    Longest word: hippopotamus
    Alice & Bob & Charlie

    map/filter กับ list comprehension

    Python มีอีกวิธีในการแปลงและกรองลิสต์ คือ list comprehension ซึ่งมักให้โค้ดที่อ่านง่ายกว่า map() และ filter() ลองมาเปรียบเทียบกัน

    ใช้ map() และ filter()
    numbers = [1, 2, 3, 4, 5, 6]
    
    # Double even numbers
    evens = filter(lambda x: x % 2 == 0, numbers)
    result = list(map(lambda x: x * 2, evens))
    print(result)  # [4, 8, 12]
    ใช้ list comprehension
    numbers = [1, 2, 3, 4, 5, 6]
    
    # Double even numbers
    result = [x * 2 for x in numbers if x % 2 == 0]
    print(result)  # [4, 8, 12]

    list comprehension ทำทั้งการกรองและการแปลงได้ในบรรทัดเดียวที่อ่านง่าย สำหรับการแปลงแบบง่าย ๆ comprehension มักเป็นทางเลือกที่ดีกว่าใน Python

    แล้วเมื่อไรควรใช้ map() และ filter() ทั้งสองมีประโยชน์เมื่อคุณมีฟังก์ชันที่มีชื่ออยู่แล้วและต้องการนำไปใช้:

    map() โดดเด่นเมื่อใช้กับฟังก์ชันที่มีชื่อ
    # When you have a named function, map() reads nicely
    names = ["alice", "bob", "charlie"]
    print(list(map(str.capitalize, names)))
    # Equivalent comprehension - a bit more verbose
    print([name.capitalize() for name in names])
    ผลลัพธ์
    ['Alice', 'Bob', 'Charlie']
    ['Alice', 'Bob', 'Charlie']

    แบบฝึกหัด

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

    แบบฝึกหัดที่ 1: ยกกำลังสองตัวเลขทุกตัว
    เขียนโค้ด

    ใช้ map() กับ lambda เพื่อยกกำลังสองตัวเลขทุกตัวในลิสต์ [1, 2, 3, 4, 5]

    แสดงผลลัพธ์เป็นลิสต์

    ผลลัพธ์ที่ต้องการ:

    [1, 4, 9, 16, 25]
    numbers = [1, 2, 3, 4, 5]
    # Use map() to square each number
    แบบฝึกหัดที่ 2: กรองคำสั้นออก
    เขียนโค้ด

    ใช้ filter() กับ lambda เพื่อเก็บไว้เฉพาะคำที่มีมากกว่า 3 อักขระ

    ข้อมูลที่ให้: ["hi", "hello", "hey", "world", "ok", "python"]

    แสดงผลลัพธ์เป็นลิสต์

    ผลลัพธ์ที่ต้องการ:

    ['hello', 'world', 'python']
    words = ["hi", "hello", "hey", "world", "ok", "python"]
    # Use filter() to keep words longer than 3 characters
    แบบฝึกหัดที่ 3: ทายผลลัพธ์ของไปป์ไลน์
    ทายผลลัพธ์

    อ่านโค้ดนี้แล้วทายผลลัพธ์ จากนั้นเขียนคำสั่ง print() ที่ให้ผลลัพธ์เหมือนกันทุกประการ

    numbers = [10, 25, 30, 45, 50]
    result = list(map(lambda x: x // 10, filter(lambda x: x >= 30, numbers)))
    print(result)

    คิดทีละขั้น: filter เก็บอะไรไว้ แล้ว map ทำอะไรกับข้อมูลแต่ละตัว

    # Print the exact output
    แบบฝึกหัดที่ 4: แก้ไปป์ไลน์ที่พัง
    แก้บั๊ก

    โค้ดนี้ควรแปลงลิสต์ของสตริงอุณหภูมิอย่าง ["32F", "100F", "212F"] ให้เป็นจำนวนเต็มในหน่วยองศาเซลเซียส แต่มีบั๊กอยู่สองจุด

    ผลลัพธ์ที่ต้องการ:

    [0, 38, 100]

    สูตร: เซลเซียส = (ฟาเรนไฮต์ - 32) * 5 / 9 แล้วปัดเป็นจำนวนเต็มที่ใกล้ที่สุด

    temps = ["32F", "100F", "212F"]
    # Bug 1: Need to extract the number from the string
    # Bug 2: Formula or conversion issue
    result = list(map(lambda t: int((t - 32) * 5 / 9), temps))
    print(result)
    แบบฝึกหัดที่ 5: หาผลคูณด้วย reduce()
    เขียนโค้ด

    ใช้ reduce() จากโมดูล functools เพื่อคำนวณผลคูณของตัวเลขทั้งหมดในลิสต์ [2, 3, 4, 5]

    ผลคูณคือ 2 * 3 * 4 * 5 = 120

    แสดงผลลัพธ์

    ผลลัพธ์ที่ต้องการ:

    120
    from functools import reduce
    numbers = [2, 3, 4, 5]
    # Use reduce() to multiply all numbers together
    แบบฝึกหัดที่ 6: สร้างไปป์ไลน์ข้อมูล
    เขียนโค้ด

    จากลิสต์คะแนนของนักเรียน ให้สร้างไปป์ไลน์ที่:

    1. กรองคะแนนที่สอบตกออก (เก็บไว้เฉพาะคะแนน >= 60)

    2. เพิ่มคะแนนที่สอบผ่านแต่ละตัวอีก 10 คะแนน (ใช้ map)

    3. จำกัดคะแนนไม่ให้เกิน 100 (ใช้ map กับ min)

    ข้อมูลที่ให้: [45, 82, 55, 91, 67, 38, 73, 95]

    แสดงลิสต์สุดท้าย

    ผลลัพธ์ที่ต้องการ:

    [92, 100, 77, 83, 100]

    คำใบ้: ถ้าจะจำกัดไม่ให้เกิน 100 ให้ใช้ min(score, 100)

    scores = [45, 82, 55, 91, 67, 38, 73, 95]
    # Step 1: Filter passing scores (>= 60)
    # Step 2: Add 10 bonus points
    # Step 3: Cap at 100
    แบบฝึกหัดที่ 7: ปรับโค้ดให้เป็น list comprehension
    ปรับปรุงโค้ด

    ปรับโครงสร้าง (refactor) ไปป์ไลน์ map/filter นี้ให้เป็น list comprehension เดียวที่ให้ผลลัพธ์เหมือนกัน

    โค้ดเดิม:

    words = ["Hello", "WORLD", "Python", "CODE", "test"]
    result = list(map(str.lower, filter(lambda w: len(w) > 4, words)))

    เขียน list comprehension ที่ให้ผลลัพธ์เดียวกันแล้วแสดงผล

    ผลลัพธ์ที่ต้องการ:

    ['hello', 'world', 'python']
    words = ["Hello", "WORLD", "Python", "CODE", "test"]
    # Refactor to a list comprehension
    result = [] # Replace with your comprehension
    print(result)

    สรุป

    สรุปสิ่งที่ได้เรียนรู้เกี่ยวกับเครื่องมือเชิงฟังก์ชันใน Python:

    ฟังก์ชันหน้าที่คืนค่า
    map(fn, iterable)แปลงข้อมูลทุกตัวออบเจกต์ map (lazy)
    filter(fn, iterable)เก็บข้อมูลที่ fn คืนค่า Trueออบเจกต์ filter (lazy)
    reduce(fn, iterable)รวมข้อมูลทุกตัวให้เหลือค่าเดียวค่าเดียว
    ฟังก์ชันอันดับสูงฟังก์ชันที่รับหรือคืนค่าเป็นฟังก์ชันขึ้นอยู่กับฟังก์ชันนั้น

    ข้อสรุปเชิงปฏิบัติ: ใช้ list comprehension กับงานแปลงหรือกรองข้อมูลส่วนใหญ่ใน Python เลือกใช้ map() และ filter() เมื่อมีฟังก์ชันที่มีชื่อให้นำไปใช้ หรือต้องการการประมวลผลแบบ lazy และใช้ reduce() ให้น้อย เพราะฟังก์ชันในตัวของ Python รองรับกรณีส่วนใหญ่ได้ดีกว่า

    เรียนอะไรต่อ

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

    บทเรียนที่เกี่ยวข้อง

    บทเรียนนี้มีประโยชน์หรือไม่