Bài 5.2. Python với nested loop

Chào mừng bạn đến với bài học về vòng lặp lồng nhau (nested loops) trong Python! Nếu bạn đã làm quen với các vòng lặp cơ bản như forwhile, thì nested loops chính là bước tiếp theo để bạn mở khóa khả năng xử lý các tác vụ phức tạp hơn, đặc biệt là khi làm việc với dữ liệu có cấu trúc đa chiều hoặc cần lặp lại một quy trình nhiều lần theo những cách khác nhau.

Hãy tưởng tượng bạn có một chiếc đồng hồ. Kim giây quay một vòng đầy đủ (60 bước) cho mỗi phút trôi qua. Kim phút cũng quay một vòng đầy đủ (60 bước) cho mỗi giờ trôi qua. Đó chính là ý tưởng cốt lõi của nested loops: một vòng lặp chạy hoàn toàn bên trong một lần lặp của vòng lặp khác.

Nested Loop là gì?

Đơn giản là một vòng lặp nằm bên trong một vòng lặp khác.

  • Vòng lặp bên ngoài (outer loop) sẽ chạy trước.
  • Với mỗi một lần lặp của vòng lặp bên ngoài, vòng lặp bên trong (inner loop) sẽ chạy hoàn tất toàn bộ chu trình của nó.
  • Sau khi vòng lặp bên trong chạy xong, vòng lặp bên ngoài mới tiếp tục lần lặp tiếp theo của nó.

Điều này tạo ra một hiệu ứng nhân lên về số lần thực thi code bên trong vòng lặp sâu nhất. Nếu vòng lặp ngoài chạy N lần và vòng lặp trong chạy M lần cho mỗi lần lặp của vòng ngoài, thì đoạn code bên trong vòng lặp trong sẽ thực thi tổng cộng N * M lần.

Cú pháp Nested Loop

Bạn có thể lồng các loại vòng lặp khác nhau (ví dụ: for trong while hoặc ngược lại), nhưng phổ biến nhất là lồng hai vòng lặp for với nhau.

Cú pháp lồng vòng lặp for:

for bien_lap_ngoai in day_lap_ngoai:
    # Code thực thi ở vòng lặp ngoài (trước vòng lặp trong)
    print(f"Bắt đầu vòng lặp ngoài với giá trị: {bien_lap_ngoai}") # Ví dụ

    for bien_lap_trong in day_lap_trong:
        # Code thực thi ở vòng lặp trong
        # Khối code này sẽ chạy M lần cho mỗi lần chạy của vòng lặp ngoài
        print(f"  -> Vòng lặp trong với giá trị: {bien_lap_trong}") # Ví dụ

    # Code thực thi ở vòng lặp ngoài (sau khi vòng lặp trong kết thúc)
    print(f"Kết thúc vòng lặp trong cho giá trị ngoài: {bien_lap_ngoai}") # Ví dụ
    print("-" * 20) # Phân cách cho dễ nhìn

Cú pháp lồng vòng lặp while:

# Khởi tạo biến điều kiện cho vòng lặp ngoài
i_ngoai = 0
while dieu_kien_ngoai(i_ngoai):
    # Code vòng lặp ngoài

    # Khởi tạo/reset biến điều kiện cho vòng lặp trong *mỗi lần* lặp ngoài
    j_trong = 0
    while dieu_kien_trong(j_trong):
        # Code vòng lặp trong
        # Cập nhật biến điều kiện trong
        j_trong += 1

    # Cập nhật biến điều kiện ngoài
    i_ngoai += 1
    # Code vòng lặp ngoài khác

Hãy cùng đi sâu vào các ví dụ để thấy rõ sức mạnh và cách ứng dụng của nested loops!

Ví dụ Minh Họa

Ví dụ 1: In Tọa độ đơn giản

Đây là ví dụ cơ bản nhất để thấy cách hai vòng lặp tương tác. Chúng ta sẽ in ra các cặp tọa độ (x, y).

print("--- Ví dụ 1: In tọa độ ---")
for x in range(3):  # Vòng lặp ngoài: x chạy từ 0 đến 2
    print(f"Vòng lặp ngoài: x = {x}")
    for y in range(2):  # Vòng lặp trong: y chạy từ 0 đến 1
        print(f"  -> Vòng lặp trong: y = {y}. Tọa độ (x, y) = ({x}, {y})")
    print(f"Kết thúc vòng lặp trong cho x = {x}\n")

# Output mong đợi:
# --- Ví dụ 1: In tọa độ ---
# Vòng lặp ngoài: x = 0
#   -> Vòng lặp trong: y = 0. Tọa độ (x, y) = (0, 0)
#   -> Vòng lặp trong: y = 1. Tọa độ (x, y) = (0, 1)
# Kết thúc vòng lặp trong cho x = 0
#
# Vòng lặp ngoài: x = 1
#   -> Vòng lặp trong: y = 0. Tọa độ (x, y) = (1, 0)
#   -> Vòng lặp trong: y = 1. Tọa độ (x, y) = (1, 1)
# Kết thúc vòng lặp trong cho x = 1
#
# Vòng lặp ngoài: x = 2
#   -> Vòng lặp trong: y = 0. Tọa độ (x, y) = (2, 0)
#   -> Vòng lặp trong: y = 1. Tọa độ (x, y) = (2, 1)
# Kết thúc vòng lặp trong cho x = 2

Giải thích:

  1. Vòng lặp ngoài bắt đầu với x = 0.
  2. Vòng lặp trong bắt đầu chạy. Nó chạy hoàn toàn với y = 0, rồi y = 1.
  3. Vòng lặp trong kết thúc cho lần lặp x = 0.
  4. Vòng lặp ngoài chuyển sang lần lặp tiếp theo, x = 1.
  5. Vòng lặp trong lại bắt đầu từ đầu và chạy hoàn toàn với y = 0, rồi y = 1.
  6. Quá trình này lặp lại cho đến khi vòng lặp ngoài kết thúc (khi x = 2).

Bạn thấy đấy, vòng lặp y (bên trong) đã chạy toàn bộ 2 lần của nó cho mỗi một lần chạy của vòng lặp x (bên ngoài).

Ví dụ 2: Xử lý Ma trận (List of Lists)

Nested loops là công cụ cực kỳ hữu ích khi làm việc với cấu trúc dữ liệu 2 chiều như ma trận (thường được biểu diễn bằng list chứa các list khác trong Python).

print("\n--- Ví dụ 2: Xử lý Ma trận ---")
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

tong_cac_phan_tu = 0
so_hang = len(matrix) # Số lượng list con (hàng)

print("Duyệt qua ma trận:")
for i in range(so_hang): # Vòng lặp ngoài: duyệt qua từng hàng (list con)
    so_cot = len(matrix[i]) # Số lượng phần tử trong hàng hiện tại (cột)
    print(f"Hàng {i}: {matrix[i]}")
    for j in range(so_cot): # Vòng lặp trong: duyệt qua từng phần tử trong hàng hiện tại
        phan_tu = matrix[i][j]
        print(f"  Phần tử tại vị trí ({i}, {j}): {phan_tu}")
        tong_cac_phan_tu += phan_tu

print(f"\nTổng tất cả các phần tử trong ma trận: {tong_cac_phan_tu}")

# Output mong đợi:
# --- Ví dụ 2: Xử lý Ma trận ---
# Duyệt qua ma trận:
# Hàng 0: [1, 2, 3]
#   Phần tử tại vị trí (0, 0): 1
#   Phần tử tại vị trí (0, 1): 2
#   Phần tử tại vị trí (0, 2): 3
# Hàng 1: [4, 5, 6]
#   Phần tử tại vị trí (1, 0): 4
#   Phần tử tại vị trí (1, 1): 5
#   Phần tử tại vị trí (1, 2): 6
# Hàng 2: [7, 8, 9]
#   Phần tử tại vị trí (2, 0): 7
#   Phần tử tại vị trí (2, 1): 8
#   Phần tử tại vị trí (2, 2): 9
#
# Tổng tất cả các phần tử trong ma trận: 45

Giải thích:

  1. Vòng lặp ngoài (for i in range(so_hang)) duyệt qua các chỉ số của matrix. Mỗi i tương ứng với một hàng (một list con).
  2. Vòng lặp trong (for j in range(so_cot)) duyệt qua các chỉ số của hàng hiện tại (matrix[i]). Mỗi j tương ứng với một cột trong hàng đó.
  3. Bằng cách sử dụng matrix[i][j], chúng ta có thể truy cập từng phần tử riêng lẻ trong ma trận.
  4. Trong ví dụ này, chúng ta in ra từng phần tử và cộng dồn chúng vào biến tong_cac_phan_tu.

Đây là cách tiếp cận tiêu chuẩn để truy cập mọi phần tử trong một cấu trúc dữ liệu 2 chiều.

Ví dụ 3: Vẽ Mẫu hình (Pattern)

Nested loops thường được dùng để tạo ra các mẫu hình văn bản thú vị. Hãy thử vẽ một tam giác vuông bằng dấu sao *.

print("\n--- Ví dụ 3: Vẽ Tam giác ---")
chieu_cao = 5
for i in range(1, chieu_cao + 1): # Vòng lặp ngoài: kiểm soát số hàng
    # Vòng lặp trong: kiểm soát số lượng '*' trên mỗi hàng
    # Số lượng '*' bằng với số thứ tự của hàng hiện tại (i)
    for j in range(i):
        print("* ", end="") # In '*' và một khoảng trắng, không xuống dòng
    print() # Xuống dòng sau khi in xong một hàng

# Output mong đợi:
# --- Ví dụ 3: Vẽ Tam giác ---
# *
# * *
# * * *
# * * * *
# * * * * *

Giải thích:

  1. Vòng lặp ngoài (for i in range(1, chieu_cao + 1)) chạy chieu_cao lần, tương ứng với số hàng của tam giác. Biến i sẽ nhận các giá trị từ 1 đến 5.
  2. Vòng lặp trong (for j in range(i)) có số lần lặp phụ thuộc vào giá trị hiện tại của i từ vòng lặp ngoài.
    • Khi i = 1, vòng lặp trong chạy range(1) (tức là 1 lần, j=0), in ra 1 dấu *.
    • Khi i = 2, vòng lặp trong chạy range(2) (tức là 2 lần, j=0, 1), in ra 2 dấu *.
    • ...
    • Khi i = 5, vòng lặp trong chạy range(5) (tức là 5 lần, j=0, 1, 2, 3, 4), in ra 5 dấu *.
  3. print("* ", end="") in dấu sao và khoảng trắng nhưng không tự động xuống dòng.
  4. print() được gọi sau khi vòng lặp trong kết thúc (nhưng vẫn bên trong vòng lặp ngoài) để xuống dòng, chuẩn bị cho hàng tiếp theo.

Sự phụ thuộc của vòng lặp trong vào biến của vòng lặp ngoài là chìa khóa để tạo ra các mẫu hình thay đổi theo từng hàng.

Ví dụ 4: Tạo Bảng cửu chương

Một ứng dụng kinh điển khác của nested loops là tạo bảng cửu chương.

print("\n--- Ví dụ 4: Bảng cửu chương ---")
for i in range(1, 10): # Vòng lặp ngoài: các số từ 1 đến 9
    print(f"--- Bảng cửu chương {i} ---")
    for j in range(1, 11): # Vòng lặp trong: nhân với các số từ 1 đến 10
        ket_qua = i * j
        print(f"{i} x {j} = {ket_qua}")
    print("-" * 25) # Phân cách giữa các bảng cửu chương

# Output mong đợi: (Chỉ hiển thị một phần)
# --- Ví dụ 4: Bảng cửu chương ---
# --- Bảng cửu chương 1 ---
# 1 x 1 = 1
# 1 x 2 = 2
# ...
# 1 x 10 = 10
# -------------------------
# --- Bảng cửu chương 2 ---
# 2 x 1 = 2
# 2 x 2 = 4
# ...
# 2 x 10 = 20
# -------------------------
# ... (tiếp tục cho đến bảng 9)

Giải thích:

  1. Vòng lặp ngoài (for i in range(1, 10)) chọn số mà chúng ta muốn tạo bảng cửu chương (từ 1 đến 9).
  2. Vòng lặp trong (for j in range(1, 11)) lặp qua các số từ 1 đến 10 để nhân với số i hiện tại.
  3. Bên trong vòng lặp trong, chúng ta thực hiện phép nhân i * j và in ra kết quả theo định dạng quen thuộc.
  4. Sau khi vòng lặp trong hoàn thành (đã in đủ 10 phép nhân cho i), chúng ta in một dòng phân cách trước khi vòng lặp ngoài chuyển sang giá trị i tiếp theo.

Lưu ý khi sử dụng Nested Loops

  1. Hiệu suất (Performance): Hãy cẩn thận khi sử dụng nested loops với các tập dữ liệu lớn. Như đã đề cập, số lần thực thi của code bên trong cùng tăng lên rất nhanh (ví dụ: N*M cho 2 vòng lặp, N*M*P cho 3 vòng lặp). Nếu N, M, P lớn, chương trình có thể trở nên rất chậm. Đôi khi có những cách tối ưu hơn (ví dụ: sử dụng thư viện như NumPy cho ma trận, hoặc thay đổi thuật toán).
  2. Độ phức tạp và Khả năng đọc (Readability): Code với quá nhiều vòng lặp lồng nhau (3, 4 hoặc nhiều hơn) có thể trở nên rất khó đọc và khó gỡ lỗi. Nếu bạn thấy mình cần lồng quá sâu, hãy cân nhắc việc tách logic thành các hàm nhỏ hơn, dễ quản lý hơn.
  3. breakcontinue trong Nested Loops: Các câu lệnh breakcontinue chỉ ảnh hưởng đến vòng lặp gần nhất mà chúng nằm bên trong.

    • break sẽ thoát khỏi vòng lặp trong cùng. Vòng lặp ngoài sẽ tiếp tục chạy bình thường.
    • continue sẽ bỏ qua phần còn lại của lần lặp hiện tại của vòng lặp trong cùng và chuyển sang lần lặp tiếp theo của vòng lặp đó.
    print("\n--- Lưu ý về break trong nested loop ---")
    for i in range(3):
        print(f"Outer loop: i = {i}")
        for j in range(5):
            if i == 1 and j == 2:
                print(f"  Inner loop: Breaking at j = {j} when i = {i}")
                break # Chỉ thoát khỏi vòng lặp j
            print(f"  Inner loop: j = {j}")
        # Code này vẫn chạy sau khi break vòng lặp trong
        print(f"End of inner loop for i = {i}")
    

    Trong ví dụ trên, khi i = 1j = 2, break được thực thi. Nó chỉ dừng vòng lặp j cho i = 1. Vòng lặp i vẫn tiếp tục với i = 2.

Nested loops là một công cụ mạnh mẽ và cần thiết trong lập trình Python. Bằng cách hiểu rõ cách chúng hoạt động và thực hành qua các ví dụ, bạn sẽ có thể áp dụng chúng hiệu quả để giải quyết nhiều bài toán thú vị!

Comments

There are no comments at the moment.