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

ลูปซ้อนใน Python: รูปแบบ ความเร็ว และเมื่อไรควรเลี่ยง

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

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

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

ลองนึกถึงสเปรดชีต ซึ่งมีแถวและคอลัมน์ ถ้าจะอ่านทุกเซลล์ คุณต้องไล่ไปทีละแถว และในแต่ละแถว ก็ไล่ไปทีละคอลัมน์ นั่นคือลูปสองตัว ตัวหนึ่งอยู่ข้างในอีกตัว นี่แหละคือ ลูปซ้อน (nested loop)

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

ในบทเรียนนี้ คุณจะได้เรียนว่าลูปซ้อนทำงานอย่างไร ดูตัวอย่างใช้งานจริงกับกริดและการจับคู่ เข้าใจว่าทำไมลูปซ้อนถึงอาจช้า และรู้ว่าเมื่อไรควรใช้วิธีอื่นแทน

ลูปซ้อนใน Python ทำงานอย่างไร

ลูปซ้อนคือลูปที่อยู่ภายในลูปอีกตัวหนึ่ง ลูปนอก (outer loop) ทำงานก่อน ในแต่ละรอบของลูปนอก ลูปใน (inner loop) จะทำงานจนครบตั้งแต่ต้นจนจบ

ลูปซ้อนแบบพื้นฐาน
for row in range(1, 4): # Outer loop: rows 1, 2, 3
for col in range(1, 4): # Inner loop: columns 1, 2, 3
print(f'({row},{col})', end=' ')
print() # New line after each row
ผลลัพธ์
(1,1) (1,2) (1,3) 
(2,1) (2,2) (2,3) 
(3,1) (3,2) (3,3)

ประเด็นสำคัญคือ ถ้าลูปนอกทำงาน n รอบ และลูปในทำงาน m รอบ โค้ดภายในลูปในจะทำงานทั้งหมด n x m ครั้ง สามแถวกับสามคอลัมน์ หมายความว่า print() ในลูปในทำงาน 9 ครั้ง

นับจำนวนรอบของลูปซ้อน
# Counting total iterations
count = 0
for i in range(5): # 5 iterations
for j in range(3): # 3 iterations each time
count += 1
print(f'Total iterations: {count}') # 5 x 3 = 15
ผลลัพธ์
Total iterations: 15

ทำงานกับข้อมูล 2 มิติ (กริดและตาราง)

การใช้งานลูปซ้อนที่พบบ่อยที่สุดคือการทำงานกับข้อมูลสองมิติ ลิสต์ซ้อนลิสต์ (list of lists) ใน Python ใช้แทนกริด ตาราง หรือเมทริกซ์ ลูปนอกไล่ไปตามแถว และลูปในไล่ไปตามคอลัมน์ในแต่ละแถว

วนลูปผ่านกริด 2 มิติ
# A 3x3 grid represented as a list of lists
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Print every element
for row in grid:
for value in row:
print(value, end=' ')
print() # New line after each row
ผลลัพธ์
1 2 3 
4 5 6 
7 8 9

บางครั้งคุณต้องการดัชนีของแถวและคอลัมน์ ไม่ใช่แค่ค่า กรณีนี้ให้ใช้ enumerate()

ใช้ enumerate กับลูปซ้อน
grid = [
['X', 'O', 'X'],
['O', 'X', 'O'],
['O', 'O', 'X']
]
# Find all positions where X appears
for r, row in enumerate(grid):
for c, cell in enumerate(row):
if cell == 'X':
print(f'X found at row {r}, col {c}')
ผลลัพธ์
X found at row 0, col 0
X found at row 0, col 2
X found at row 1, col 1
X found at row 2, col 2

ลองดูตัวอย่างใช้งานจริง: สร้างตารางสูตรคูณ

ตารางสูตรคูณด้วยลูปซ้อน
# Multiplication table (1 through 5)
for row in range(1, 6):
for col in range(1, 6):
product = row * col
print(f'{product:4}', end='')
print()
ผลลัพธ์
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25

สร้างการจับคู่ทุกแบบด้วยลูปซ้อน

ลูปซ้อนเหมาะมากสำหรับสร้างการจับคู่ทุกแบบของสมาชิกจากสองกลุ่ม (หรือมากกว่า) ลูปนอกเลือกจากกลุ่มแรก และลูปในเลือกจากกลุ่มที่สอง

จับคู่สีกับขนาดทุกแบบ
colors = ['red', 'blue']
sizes = ['S', 'M', 'L']
print('All t-shirt options:')
for color in colors:
for size in sizes:
print(f' {color} - {size}')
ผลลัพธ์
All t-shirt options:
  red - S
  red - M
  red - L
  blue - S
  blue - M
  blue - L

มี 2 สีและ 3 ขนาด จะได้ 6 แบบ ทุกสีจับคู่กับทุกขนาด เรียกว่า ผลคูณคาร์ทีเซียน (Cartesian product) ของลิสต์ทั้งสอง

หาคู่ตัวเลขที่ผลรวมเท่ากับค่าเป้าหมาย
# Finding pairs that add up to a target
numbers = [1, 3, 5, 7, 9]
target = 10
print(f'Pairs that sum to {target}:')
for i in range(len(numbers)):
for j in range(i + 1, len(numbers)): # Start j after i to avoid duplicates
if numbers[i] + numbers[j] == target:
print(f' {numbers[i]} + {numbers[j]} = {target}')
ผลลัพธ์
Pairs that sum to 10:
  1 + 9 = 10
  3 + 7 = 10

สร้างรูปแบบด้วยลูปซ้อน

การแสดงรูปแบบ (pattern) เป็นแบบฝึกหัดคลาสสิกเพื่อทำความเข้าใจลูปซ้อน ลูปนอกควบคุมแต่ละแถว และลูปในควบคุมจำนวนตัวอักษรในแต่ละแถว

สามเหลี่ยมมุมฉากจากดาว
# Right triangle pattern
n = 5
for row in range(1, n + 1):
for col in range(row):
print('*', end='')
print()
ผลลัพธ์
*
**
***
****
*****

เคล็ดลับคือ ช่วง (range) ของลูปในขึ้นอยู่กับตัวแปรของลูปนอก แถวที่ 1 ลูปในทำงานครั้งเดียว แถวที่ 2 ทำงานสองครั้ง แถวที่ 5 ทำงานห้าครั้ง จึงเกิดเป็นรูปขั้นบันได

สามเหลี่ยมกลับหัว (วิธีที่ง่ายกว่า)
# Inverted triangle
n = 5
for row in range(n, 0, -1):
print('*' * row)
ผลลัพธ์
*****
****
***
**
*

ประสิทธิภาพ: ทำไมลูปซ้อนถึงอาจช้า

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

นักวิทยาการคอมพิวเตอร์อธิบายเรื่องนี้ด้วย สัญกรณ์บิ๊กโอ (Big O notation) ลูปเดียวคือ O(n) เวลาที่ใช้เพิ่มขึ้นเป็นเส้นตรงตามขนาดข้อมูล ลูปซ้อนคือ O(n²) เวลาที่ใช้เพิ่มขึ้นตามกำลังสองของขนาดข้อมูล ถ้าข้อมูลเพิ่มเป็นสองเท่า งานจะเพิ่มเป็นสี่เท่า

เปรียบเทียบความเร็วของลูปเดียวกับลูปซ้อน
import time
# O(n) - single loop
n = 2000
start = time.time()
total = 0
for i in range(n):
total += i
single_time = time.time() - start
# O(n²) - nested loop
start = time.time()
total = 0
for i in range(n):
for j in range(n):
total += 1
nested_time = time.time() - start
print(f'Single loop: {single_time:.4f} seconds')
print(f'Nested loop: {nested_time:.4f} seconds')
print(f'Nested is {nested_time / max(single_time, 0.0001):.0f}x slower')
ผลลัพธ์
Single loop: 0.0000 seconds
Nested loop: 0.8120 seconds
Nested is 8120x slower

เมื่อไรควรเลี่ยงลูปซ้อน

ไม่ใช่ทุกปัญหาที่ดูเหมือนต้องใช้ลูปซ้อนจะต้องใช้จริง บ่อยครั้งมีวิธีที่เร็วกว่าโดยใช้เซต ดิกชันนารี หรือฟังก์ชันในตัว (built-in)

O(n²): ใช้ลูปซ้อนตรวจหาข้อมูลซ้ำ
nums = [3, 1, 4, 1, 5, 9]
has_dup = False
for i in range(len(nums)):
    for j in range(i+1, len(nums)):
        if nums[i] == nums[j]:
            has_dup = True
print(has_dup)  # True
O(n): ใช้เซตแทน
nums = [3, 1, 4, 1, 5, 9]
has_dup = len(nums) != len(set(nums))
print(has_dup)  # True

แบบที่ใช้เซตไม่ได้แค่สั้นกว่า แต่เร็วกว่าอย่างมาก สำหรับข้อมูล 10,000 ตัว แบบลูปซ้อนต้องเปรียบเทียบ 50 ล้านครั้ง ส่วนแบบเซตทำงานราว 10,000 ครั้ง เร็วกว่าถึง 5,000 เท่า

O(n²): ใช้ลูปซ้อนหาค่า
students = [
    {'name': 'Alice', 'id': 101},
    {'name': 'Bob', 'id': 102},
    {'name': 'Charlie', 'id': 103}
]
ids_to_find = [103, 101]

for target_id in ids_to_find:
    for s in students:
        if s['id'] == target_id:
            print(s['name'])
O(n): สร้างดิกชันนารีก่อน
students = [
    {'name': 'Alice', 'id': 101},
    {'name': 'Bob', 'id': 102},
    {'name': 'Charlie', 'id': 103}
]
ids_to_find = [103, 101]

lookup = {s['id']: s['name'] for s in students}
for target_id in ids_to_find:
    print(lookup[target_id])

แบบฝึกหัด

แบบฝึกหัดเหล่านี้จะช่วยให้คุณเข้าใจลูปซ้อนได้มั่นคงขึ้น เริ่มจากรูปแบบพื้นฐาน แล้วไปสู่ปัญหาในโลกจริง

แบบฝึกหัดที่ 1: แสดงรูปสี่เหลี่ยม
เขียนโค้ด

แสดงรูปสี่เหลี่ยมจากดาว 3 แถว 5 คอลัมน์ แต่ละแถวมีดาว 5 ดวงโดยไม่มีช่องว่างคั่น

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

*****
*****
*****
# Print a 3x5 rectangle of stars
แบบฝึกหัดที่ 2: ทายผลลัพธ์
ทายผลลัพธ์

ทายว่าโค้ดจะแสดงอะไรโดยยังไม่ต้องรัน นับจำนวนบรรทัดทั้งหมดในผลลัพธ์ จากนั้นจึงรันเพื่อตรวจคำตอบ

for i in range(3):
for j in range(2):
print(f'{i}-{j}')
แบบฝึกหัดที่ 3: สามเหลี่ยมตัวเลข
เขียนโค้ด

แสดงสามเหลี่ยมตัวเลขที่แต่ละแถวมีหมายเลขแถวซ้ำกัน สามเหลี่ยมนี้มี 4 แถว

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

1
22
333
4444
# Print a number triangle with 4 rows
แบบฝึกหัดที่ 4: รวมค่าทั้งหมดในกริด
เขียนโค้ด

มีกริดของตัวเลขให้ไว้ ใช้ลูปซ้อนคำนวณและแสดงผลรวมของค่าทั้งหมดในกริด

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

45
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# Calculate the sum of all values
แบบฝึกหัดที่ 5: ชุดแต่งตัวทุกแบบ
เขียนโค้ด

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

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

shirt + jeans
shirt + shorts
sweater + jeans
sweater + shorts
hoodie + jeans
hoodie + shorts
Total outfits: 6
tops = ['shirt', 'sweater', 'hoodie']
bottoms = ['jeans', 'shorts']
# Print all combinations and the total count
แบบฝึกหัดที่ 6: แก้กริดพิกัด
แก้บั๊ก

โค้ดนี้ควรแสดงกริดพิกัดขนาด 3x3 แต่มีบั๊ก ผลลัพธ์ควรแสดงพิกัดหนึ่งแถวต่อหนึ่งบรรทัด โดย x ไล่จาก 0 ถึง 2 และ y ไล่จาก 0 ถึง 2

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

(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)

หาบั๊กแล้วแก้ไข

for x in range(3):
for y in range(3):
print(f'({x},{y})', end=' ')
print()

สรุป: ลูปซ้อนใน Python

สรุปสิ่งที่คุณเรียนเกี่ยวกับลูปซ้อน:

แนวคิดใจความสำคัญ
ลูปซ้อนลูปที่อยู่ภายในลูปอีกตัว
จำนวนรอบทั้งหมดจำนวนรอบลูปนอก x จำนวนรอบลูปใน
ข้อมูล 2 มิติลูปนอก = แถว, ลูปใน = คอลัมน์
การจับคู่ทุกแบบจับคู่สมาชิกทุกตัวของกลุ่ม A กับสมาชิกทุกตัวของกลุ่ม B
ประสิทธิภาพO(n²): ข้อมูลเพิ่มสองเท่า งานเพิ่มสี่เท่า
ทางเลือกอื่นเซตและดิกชันนารีมักใช้แทนลูปซ้อนได้

หลักง่าย ๆ ที่ควรจำ:

  • คิดถึงขนาดข้อมูลก่อนเขียนลูปซ้อน
  • ตั้งชื่อตัวแปรให้มีความหมาย (row/col ไม่ใช่ i/j)
  • การคูณสตริง ('*' * n) มักใช้แทนลูปในได้เมื่อสร้างรูปแบบ
  • เมื่อค้นหาข้อมูล ดิกชันนารีหรือเซตเกือบจะเร็วกว่าลูปซ้อนเสมอ
  • เรียนอะไรต่อ

    ในบทถัดไป วิธีอ่านข้อความแจ้งข้อผิดพลาดของ Python คุณจะได้เรียนวิธีถอดความหมายของข้อความแจ้งข้อผิดพลาด แก้บั๊กที่พบบ่อยที่สุด และเลิกกลัว traceback การดีบักเป็นทักษะที่ทำให้ทุกอย่างง่ายขึ้น

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

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