Nhập môn Tư duy Lập trình/for và danh sách — lặp qua một tập dữ liệu
12/31
Bài 12 / 31~12 phútRẽ nhánh & lặpMiễn phí lượt xem

for và danh sách — lặp qua một tập dữ liệu

Khi đã biết trước cần duyệt qua những gì, for gọn hơn while nhiều. Đây cũng là chỗ gặp biến tích luỹ: một ô nhớ lớn dần qua từng vòng để giữ tổng, đếm, hoặc lớn nhất.

TL;DR: for lấy lần lượt từng phần tử của một danh sách và gán vào một biến, chạy thân một lần cho mỗi phần tử, rồi tự dừng khi hết. Nó lo hộ cả ba phần mà while bắt bạn tự viết, nên không có nguy cơ chạy mãi. Điều cần trace ở đây là hai biến với hai vai khác hẳn nhau: biến lặp bị ghi đè mỗi vòng và mất sạch giá trị cũ, còn biến tích luỹ thì cộng dồn và giữ lại dấu vết của mọi vòng trước. Người mới hay gộp hai vai này làm một, và đó là nguồn của phần lớn lỗi khi tính tổng hay đếm.

Ở bài trước, để đi hết một danh sách bằng while, bạn phải tự khởi tạo một biến đếm, tự tăng nó, tự nhớ dừng đúng chỗ. Ba việc, ba cơ hội sai.

Khi bạn đã biết trước cần duyệt qua những gì, có cấu trúc lo hộ cả ba. Bài này giới thiệu nó, cùng với thứ mà mọi chương trình tính tổng, đếm số lượng, hay tìm giá trị lớn nhất đều dùng: biến tích luỹ.

1. Danh sách — một ô nhớ giữ nhiều giá trị

Một ô nhớ có tên giữ đúng một giá trị tại một thời điểm. Danh sách không phá quy tắc đó: ô nhớ vẫn giữ đúng một thứ, chỉ là thứ đó là cả một dãy giá trị có thứ tự.

diem = [7, 9, 5]

print(diem)
print(len(diem))

Output thật:

[7, 9, 5]
3

Cặp ngoặc vuông tạo ra một danh sách. len() cho biết nó có bao nhiêu phần tử. Thứ tự trong danh sách là thứ tự bạn viết, và nó được giữ nguyên — đây là điều for dựa vào.

2. Analogy — chồng hồ sơ trên bàn

Bạn có một chồng hồ sơ và việc phải làm là xử lý từng cái. Bạn nhấc cái trên cùng ra, đặt vào khay đang xử lý, làm xong thì bỏ sang bên, rồi nhấc cái tiếp theo vào đúng cái khay đó. Khay chỉ chứa một hồ sơ tại một lúc, và mỗi lần bạn đặt hồ sơ mới vào là hồ sơ cũ đã rời khỏi khay.

Cạnh đó bạn có một tờ giấy tổng kết. Mỗi hồ sơ xử lý xong, bạn cộng thêm một dòng vào tờ giấy. Tờ giấy không bị xoá đi mỗi lần — nó dày lên.

Trên bànTrong code
Chồng hồ sơDanh sách
Khay đang xử lýBiến lặp — bị ghi đè mỗi vòng
Tờ giấy tổng kếtBiến tích luỹ — cộng dồn qua các vòng
Nhấc hồ sơ tiếp theo vào khayfor gán phần tử kế tiếp cho biến lặp
Hết chồng thì nghỉfor tự dừng khi hết danh sách
💡 Cách nhớ

Khay thì thay, giấy thì dày. Biến lặp mất giá trị cũ mỗi vòng; biến tích luỹ giữ lại dấu vết của mọi vòng.

3. Biến lặp và biến tích luỹ khác nhau thế nào?

diem = [7, 9, 5]
tong = 0

for d in diem:
    tong = tong + d

print(tong)

Output thật:

21

Hai biến, hai vai:

Sơ đồ: hộp nét đứt trên cùng ghi diem gán danh sách 7, 9, 5 kèm chú thích danh sách cho sẵn và mỗi vòng for lấy ra một phần tử; mũi tên xuống hàng ba hộp nối tiếp nhau, hộp vòng 1 ghi d bằng 7 và tong đi từ 0 tới 7, hộp vòng 2 ghi d bằng 9 và tong đi từ 7 tới 16, hộp nhấn màu xanh vòng 3 ghi d bằng 5 và tong đi từ 16 tới 21

Nhìn hàng d: 7, rồi 9, rồi 5. Mỗi vòng nó bị ghi đè, và giá trị cũ mất hẳn — sau vòng 2, con số 7 không còn ở đâu trong d nữa.

Nhìn hàng tong: 0, 7, 16, 21. Nó không bị ghi đè bằng một giá trị rời rạc mà bằng chính nó cộng thêm cái vừa lấy được. Đó là lý do nó giữ được dấu vết của cả ba vòng, dù mỗi vòng cũng chỉ là một phép gán bình thường.

Hai dòng nữa cùng một khuôn, đổi phép tính thì đổi ý nghĩa:

diem = [7, 9, 5]
dem_dat = 0
lon_nhat = 0

for d in diem:
    if d >= 7:
        dem_dat = dem_dat + 1
    if d > lon_nhat:
        lon_nhat = d

print(dem_dat)
print(lon_nhat)

Output thật:

2
9

dem_dat cộng 1 mỗi khi gặp phần tử thoả điều kiện — đó là đếm. lon_nhat ghi đè bằng phần tử hiện tại mỗi khi gặp cái lớn hơn — đó là tìm lớn nhất. Cả hai đều là biến tích luỹ, đều phải khởi tạo trước vòng lặp.

Giá trị khởi tạo không phải chuyện nhỏ

Khởi tạo tong = 0 vì cộng thêm 0 không đổi gì. Nhưng nếu bạn tính tích thì phải khởi tạo bằng 1, vì nhân với 0 sẽ ra 0 mãi mãi. Còn lon_nhat = 0 chỉ đúng khi mọi phần tử đều không âm — với danh sách toàn số âm, kết quả sẽ là 0, một con số không hề có trong danh sách.

4. Thử đoán — đổi chỗ một dòng

Đoạn dưới giống hệt đoạn tính tổng ở mục 3, chỉ khác đúng một chỗ: dòng khởi tạo tong = 0 bị đẩy vào trong thân vòng lặp.

diem = [7, 9, 5]

for d in diem:
    tong = 0
    tong = tong + d

print(tong)
🖊️ Thử đoán

Chương trình in ra số mấy? Viết ra giấy, kèm một câu nói rõ tong bằng bao nhiêu ở đầu mỗi vòng.

Kết quả

5. Tới lượt bạn — sắp lại một vòng lặp tích luỹ

Sắp các dòng thành chương trình cộng tiền ba món hàng rồi in tổng.

Output mong đợi:

95000
Sắp lại thứ tự dòng

Đưa từng dòng vào khung lời giải rồi xếp cho đúng thứ tự. Kho có thể chứa dòng thừa.

Kho dòng (7)

  • tong = 0
  • tong = tong + g
  • print(tong)
  • print(tong)
  • gia = [30000, 45000, 20000]
  • tong = 0
  • for g in gia:

Lời giải của bạn (0/5)

Chưa có dòng nào. Bấm “Đưa vào” ở trên.

Gợi ý: Hai dòng nhiễu đều là dòng ĐÚNG nhưng đặt sai chỗ — chúng nằm trong thân vòng lặp. Hỏi từng dòng: cái này phải chạy một lần, hay chạy lại mỗi vòng?

Hai dòng nhiễu ở đây tinh vi hơn bài trước: chúng không sai về nội dung, chỉ sai về vị trí. tong = 0 trong thân là đúng cái bẫy bạn vừa gặp ở mục 4; còn print(tong) trong thân thì in ra ba dòng thay vì một.

6. Tới lượt bạn — điền nốt bảng trace

Cột là biến, hàng là dòng lệnh vừa chạy xong. Với vòng lặp, bảng dài ra theo số vòng: cùng một dòng code xuất hiện nhiều hàng, mỗi hàng một vòng.

so = [4, 8, 6]
tong = 0
dem = 0

for s in so:
    tong = tong + s
    dem = dem + 1

print(tong)
print(dem)
Vòngstong sau vòngdem sau vòng
trước vòng lặp00
1441
2
3
🖊️ Dừng lại — điền hai hàng cuối

Chép bảng ra giấy, điền vòng 2 và vòng 3. Rồi trả lời thêm: chương trình in ra hai số nào, và nếu danh sách rỗng thì hai số đó là bao nhiêu?

Đáp án

7. Bẫy thường gặp khi dùng for

Nhầm 1 — khởi tạo biến tích luỹ trong thân. Mỗi vòng xoá sạch kết quả vòng trước, và kết quả cuối cùng chỉ phản ánh phần tử cuối.

✅ Dòng khởi tạo nằm ngoài, ngang hàng lề với for, chạy đúng một lần.

Nhầm 2 — dùng biến lặp sau khi vòng lặp kết thúc. Sau vòng cuối, biến lặp vẫn còn giá trị của phần tử cuối cùng, nên code chạy được và cho ra một con số trông hợp lý. Nhưng con số đó chỉ là phần tử cuối, không phải kết quả của cả danh sách.

✅ Kết quả của cả vòng lặp phải nằm trong biến tích luỹ, không nằm trong biến lặp.

8. 📚 Đào sâu — for trong tài liệu Python

📚 Đào sâu (không bắt buộc)

4. More Control Flow Tools — Python documentation — mục "for Statements" cho thấy đúng khuôn bạn vừa học, với ví dụ duyệt một danh sách chuỗi. Đáng đọc vì nó nói rõ for của Python duyệt các phần tử của một dãy theo đúng thứ tự chúng xuất hiện, và tự đặt mình đối lập với hai kiểu for khác: kiểu của Pascal luôn chạy qua một dãy số cách đều, còn kiểu của C thì để người viết tự định nghĩa cả bước nhảy lẫn điều kiện dừng. Đó là lý do for ở đây không thể chạy mãi — nó không hỏi một điều kiện do bạn viết, nó chỉ đi hết một dãy hữu hạn có sẵn.

9. Liên hệ các bài khác

10. Tóm tắt

  • Nghi kết quả tính tổng sai? Soi dòng khởi tạo trước: nằm trong hay ngoài for quyết định biến đó còn là biến tích luỹ hay không.
  • Tổng ra đúng bằng phần tử cuối cùng? Đó là chữ ký của dòng khởi tạo bị đặt lọt vào trong thân.
  • Kết quả là một con số không hề có trong danh sách? Kiểm giá trị khởi tạo — lon_nhat = 0 gặp dữ liệu toàn số âm sẽ ra đúng như vậy.
  • Chưa biết khởi tạo bằng mấy? Hỏi "danh sách rỗng thì đáp án đúng là gì" — câu trả lời đó chính là giá trị cần điền.
  • Cần kết quả của cả danh sách mà lại đọc biến lặp sau vòng lặp? Nó chỉ còn phần tử cuối, và điều đó không báo lỗi.

11. Tự kiểm tra

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Biến lặp và biến tích luỹ khác nhau ở điểm nào? Nêu bằng cơ chế phép gán, đừng chỉ nói một cái đổi một cái không.
  2. Q2
    Vì sao đặt tong = 0 vào trong thân vòng lặp làm kết quả thành 5 thay vì 21?
  3. Q3
    Danh sách rỗng thì thân vòng lặp chạy mấy lần, và chương trình tính tổng in ra số mấy?
  4. Q4
    Với diem = [7, 9, 5], ngay sau khi vòng thứ hai chạy xong thì dtong lần lượt bằng bao nhiêu? Vì sao từ vòng hai trở đi hai con số này không còn bằng nhau?
  5. Q5
    Vì sao vòng for trong bài không thể chạy mãi, trong khi while ở bài trước thì có thể?
  6. Q6
    Sau khi vòng lặp kết thúc, biến lặp còn giữ giá trị gì? Vì sao dùng nó ở đó là một lỗi khó phát hiện?

Bài tiếp theo: Lặp lồng nhau và lỗi lệch một đơn vị

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Lặp lồng nhau và lỗi lệch một đơn vị