Bài 23: Một số lệnh làm việc với dữ liệu danh sách – Lý thuyết

I. Lý thuyết trọng tâm

1. Duyệt danh sách với toán tử in

Toán tử inhai công dụng khi làm việc với danh sách:

a) Kiểm tra phần tử có trong danh sách không

Cú pháp:

<giá_trị> in <danh_sách>

Kết quả trả về True nếu giá trị có trong danh sách, False nếu không có.

>>> A = [1, 2, 3, 4, 5]
>>> 2 in A
True          # số 2 có trong A
>>> 10 in A
False         # số 10 không có trong A

⚠️ Lưu ý kiểu dữ liệu: Toán tử in so sánh chính xác cả giá trị lẫn kiểu dữ liệu. Số nguyên 1 và xâu kí tự "1" là hai giá trị khác nhau.

Câu hỏi kiểm tra nhanh: Cho A = ["0", "1", "01", "10"]. Các biểu thức sau trả về đúng hay sai?

  • 1 in AFalse. Vì 1 là số nguyên, trong khi danh sách A chỉ chứa các xâu kí tự ("1" ≠ 1).
  • "01" in ATrue. Vì xâu "01" có trong danh sách A.

b) Duyệt từng phần tử bằng for...in

Cú pháp:

for <biến> in <danh_sách>:
    <các lệnh>

Biến sẽ lần lượt nhận trực tiếp giá trị của từng phần tử trong danh sách (không cần dùng chỉ số và range()).

>>> A = [10, 11, 12, 13, 14, 15]
>>> for k in A:
        print(k, end=" ")
10 11 12 13 14 15

So sánh hai cách duyệt:

Cách duyệt Cú pháp Biến nhận giá trị Khi nào dùng
for i in range(len(A)) Duyệt qua chỉ số i = 0, 1, 2… → truy cập A[i] Cần biết vị trí phần tử, cần thay đổi giá trị
for k in A Duyệt qua giá trị k = giá trị từng phần tử Chỉ cần đọc giá trị, không cần chỉ số

Câu hỏi kiểm tra nhanh: Giải thích ý nghĩa từ khóa in trong câu lệnh:

for i in range(10):
    <các lệnh>

→ Ở đây in không phải kiểm tra phần tử có trong danh sách. Nó là một phần của cú pháp vòng lặp for, dùng để chỉ định biến i lần lượt nhận các giá trị trong range(10) (tức 0, 1, 2, …, 9).

2. Một số phương thức làm việc với danh sách

Ngoài append() đã học ở Bài 22, Python còn cung cấp thêm các phương thức quan trọng:

Phương thức Cú pháp Chức năng
append(x) A.append(x) Thêm phần tử x vào cuối danh sách A
insert(k, x) A.insert(k, x) Chèn phần tử x vào vị trí k, đẩy các phần tử từ k trở đi sang phải
remove(x) A.remove(x) Xóa phần tử đầu tiên có giá trị x. Nếu không tìm thấy → báo lỗi ValueError
clear() A.clear() Xóa toàn bộ danh sách, biến A thành []

Chi tiết phương thức insert()

>>> A = [1, 2, 6, 10]
>>> A.insert(2, 5)       # chèn số 5 tại chỉ số 2
>>> A
[1, 2, 5, 6, 10]

Trường hợp đặc biệt: Nếu chỉ số nằm ngoài phạm vi, lệnh vẫn hoạt động:

  • k < 0 (hoặc nhỏ hơn chỉ số đầu) → chèn vào đầu danh sách.
  • k > len(A) → chèn vào cuối danh sách.
>>> A = []
>>> A.insert(-10, 1)     # chèn 1 vào đầu (vì -10 < 0)
>>> A.insert(100, 2)     # chèn 2 vào cuối (vì 100 > len(A))
>>> A
[1, 2]

Chi tiết phương thức remove()

>>> A = [1, 2, 3, 4, 5]
>>> A.remove(1)          # xóa phần tử đầu tiên có giá trị 1
>>> A
[2, 3, 4, 5]

>>> A.remove(10)         # giá trị 10 không tồn tại → LỖI
ValueError: list.remove(x): x not in list

Câu hỏi kiểm tra nhanh: Khi nào thì lệnh A.append(1)A.insert(0, 1) có tác dụng giống nhau?

→ Khi danh sách A rỗng (A = []). Lúc đó cả hai lệnh đều cho kết quả A = [1], vì danh sách chỉ có 1 phần tử nên “đầu” và “cuối” là cùng một vị trí. Khi A không rỗng, append(1) thêm vào cuối còn insert(0, 1) chèn vào đầu → kết quả khác nhau.

Câu hỏi kiểm tra nhanh: Danh sách A trước và sau lệnh insert()[1, 4, 10, 0][1, 4, 10, 5, 0]. Lệnh đã dùng là gì?

A.insert(3, 5) — chèn giá trị 5 vào vị trí chỉ số 3 (trước phần tử 0), đẩy phần tử 0 sang phải.

II. Phương pháp giải các dạng bài tập

Dạng 1: Xác định kết quả khi dùng toán tử in và các phương thức

Phương pháp:

  • Bước 1: Viết trạng thái ban đầu của danh sách, đánh chỉ số từ 0.
  • Bước 2: Thực hiện từng lệnh theo thứ tự. Với mỗi lệnh:
    • insert(k, x) → chèn x tại vị trí k, đẩy phần tử k trở đi sang phải, độ dài +1.
    • remove(x) → tìm và xóa phần tử đầu tiên có giá trị x, độ dài −1. Nếu không tìm thấy → lỗi.
    • clear() → danh sách thành [].
    • x in A → so sánh chính xác cả giá trị và kiểu dữ liệu.
  • Bước 3: Ghi nhận trạng thái danh sách sau mỗi lệnh.

Bài tập mẫu (SGK – Câu hỏi mục 2, câu 2): Danh sách A trước là [1, 4, 10, 0], sau lệnh insert()[1, 4, 10, 5, 0]. Xác định lệnh đã dùng.

Lời giải:

So sánh hai danh sách:

Chỉ số 0 1 2 3 4
Trước 1 4 10 0
Sau 1 4 10 5 0

Số 5 được chèn vào vị trí chỉ số 3, phần tử 0 bị đẩy sang phải.

→ Lệnh: A.insert(3, 5)

Dạng 2: Chèn phần tử vào danh sách bằng insert()

Phương pháp:

  • Xác định vị trí chỉ số cần chèn (nhớ chỉ số đánh từ 0).
  • Lệnh insert(k, x) sẽ chèn x trước phần tử hiện tại ở vị trí k.
  • Sau mỗi lệnh insert, chỉ số các phần tử phía sau đều tăng thêm 1.

Bài tập mẫu (SGK – Luyện tập, bài 1): Cho dãy A = [1, 2, 2, 3, 4, 5, 5]. Viết lệnh thực hiện:

a) Chèn số 1 vào ngay sau giá trị 1 của dãy.

Giá trị 1 đang ở chỉ số 0 → cần chèn số 1 vào chỉ số 1 (ngay sau nó):

A.insert(1, 1)
# A = [1, 1, 2, 2, 3, 4, 5, 5]

b) Chèn số 3 và số 4 vào danh sách để dãy có số 3 và số 4 liền nhau hai lần.

Hiện tại dãy là [1, 2, 2, 3, 4, 5, 5] — đã có một cặp 3, 4 ở chỉ số 3 và 4. Cần chèn thêm một cặp 3, 4 ngay bên cạnh. Chèn số 3 trước chỉ số 3 (hoặc sau chỉ số 4), rồi chèn số 4:

A.insert(5, 3)   # chèn 3 sau giá trị 4
# A = [1, 2, 2, 3, 4, 3, 5, 5]
A.insert(6, 4)   # chèn 4 ngay sau số 3 vừa chèn
# A = [1, 2, 2, 3, 4, 3, 4, 5, 5]

Kết quả: [1, 2, 2, 3, 4, 3, 4, 5, 5] — số 3 và số 4 liền nhau hai lần.

Dạng 3: Xóa phần tử theo điều kiện bằng vòng lặp while + remove()

Phương pháp:

Khi cần xóa phần tử khỏi danh sách trong quá trình duyệt, không dùng for (vì xóa phần tử làm thay đổi chỉ số, dễ bỏ sót). Thay vào đó dùng while với biến đếm thủ công:

i = 0
while i < len(A):
    if <điều_kiện_xóa>:
        A.remove(A[i])    # xóa → không tăng i (vì phần tử sau dồn lên)
    else:
        i = i + 1         # không xóa → mới tăng i

Tại sao chỉ tăng i khi không xóa? Khi xóa phần tử tại vị trí i, phần tử tiếp theo dồn vào đúng vị trí i. Nếu tăng i lúc này sẽ bỏ sót phần tử vừa dồn lên.

Bài tập mẫu (SGK – Thực hành, Nhiệm vụ 2): Cho trước dãy số A. Viết chương trình xóa đi các phần tử có giá trị nhỏ hơn 0.

Lời giải:

A = [0, 1, -3, -10, 5, 9, -20, 55]
i = 0
while i < len(A):
    if A[i] < 0:
        A.remove(A[i])
    else:
        i = i + 1
print(A)

Kết quả: [0, 1, 5, 9, 55]

Mô phỏng: i=0 (A[0]=0, giữ) → i=1 (A[1]=1, giữ) → i=2 (A[2]=−3, xóa, không tăng i) → i=2 (A[2]=−10, xóa) → i=2 (A[2]=5, giữ) → i=3 (A[3]=9, giữ) → i=4 (A[4]=−20, xóa) → i=4 (A[4]=55, giữ) → i=5, kết thúc.

Dạng 4: Tìm mẫu (pattern) trong danh sách

Phương pháp:

  • Dùng vòng while duyệt từ đầu danh sách.
  • Tại mỗi vị trí i, kiểm tra xem các phần tử liên tiếp A[i], A[i+1], A[i+2]... có khớp với mẫu cần tìm không.
  • Dùng biến pkq (kết quả) để ghi nhận vị trí tìm thấy. Nếu tìm thấy → dừng. Nếu không → thông báo không tìm thấy.

Bài tập mẫu (SGK – Thực hành, Nhiệm vụ 3): Cho dãy số A. Viết chương trình tìm vị trí đầu tiên mà ba số liên tiếp có giá trị là 1, 2, 3.

Lời giải:

A = [0, 4, 0, 1, 2, 3, 8, 9, 0, 1, 2, 3, 17, -16, 0, 1, 2]
p = [1, 2, 3]
pkq = -1
i = 0
while i < len(A) - 3 and pkq == -1:
    if A[i] == p[0] and A[i+1] == p[1] and A[i+2] == p[2]:
        pkq = i
    else:
        i = i + 1
if pkq >= 0:
    print("Tìm thấy mẫu", p, "tại vị trí", pkq)
else:
    print("Không tìm thấy mẫu", p)

Kết quả: Tìm thấy mẫu [1, 2, 3] tại vị trí 3

Giải thích: Biến pkq = -1 nghĩa là chưa tìm thấy. Vòng while chạy khi chưa tìm thấy (pkq == -1) và chưa hết danh sách. Tại i=3: A[3]=1, A[4]=2, A[5]=3 khớp mẫu → pkq = 3, vòng lặp dừng.

III. Hướng dẫn thực hành

Nhiệm vụ 1: Nhập danh sách tên học sinh, in theo thứ tự ngược

Đề bài: Nhập số n từ bàn phím, nhập danh sách n tên học sinh và in ra theo thứ tự ngược lại với thứ tự nhập.

Hướng dẫn: Do yêu cầu in ngược, ta dùng insert(0, ...) để chèn mỗi tên mới vào đầu danh sách. Khi đó tên nhập sau sẽ nằm trước, tên nhập trước sẽ bị đẩy ra sau — kết quả in ra tự nhiên là thứ tự ngược.

dsLop = []
n = int(input("Nhập số học sinh trong lớp: "))
for i in range(n):
    name = input("Nhập họ tên học sinh thứ " + str(i+1) + ": ")
    dsLop.insert(0, name)
print("Danh sách học sinh đã nhập:")
for name in dsLop:
    print(name)

Giải thích:

  • dsLop.insert(0, name) — luôn chèn tên mới vào vị trí 0 (đầu danh sách), đẩy các tên đã có sang phải.
  • Vòng for name in dsLop dùng toán tử in để duyệt trực tiếp qua giá trị, in mỗi tên trên một dòng.

Ví dụ: Nhập lần lượt: An, Bình, Chi → danh sách lưu: ["Chi", "Bình", "An"] → in ra: Chi, Bình, An (ngược thứ tự nhập).

Nhiệm vụ 2: Xóa các phần tử âm khỏi danh sách

Đề bài: Cho trước dãy số A. Viết chương trình xóa đi các phần tử có giá trị nhỏ hơn 0.

Hướng dẫn: Dùng vòng while kết hợp remove(). Chú ý chỉ tăng biến đếm i khi không xóa, vì sau khi xóa phần tử phía sau dồn lên.

A = [0, 1, -3, -10, 5, 9, -20, 55]
i = 0
while i < len(A):
    if A[i] < 0:
        A.remove(A[i])
    else:
        i = i + 1
print(A)

Kết quả: [0, 1, 5, 9, 55]

Nhiệm vụ 3: Tìm mẫu [1, 2, 3] trong dãy số

Đề bài: Cho dãy số A. Tìm vị trí đầu tiên mà ba số hạng liên tiếp có giá trị là 1, 2, 3.

Hướng dẫn: Dùng biến pkq = -1 để đánh dấu chưa tìm thấy. Duyệt bằng while, kiểm tra ba phần tử liên tiếp tại mỗi vị trí, nếu khớp thì ghi nhận và dừng.

A = [0, 4, 0, 1, 2, 3, 8, 9, 0, 1, 2, 3, 17, -16, 0, 1, 2]
p = [1, 2, 3]
pkq = -1
i = 0
while i < len(A) - 3 and pkq == -1:
    if A[i] == p[0] and A[i+1] == p[1] and A[i+2] == p[2]:
        pkq = i
    else:
        i = i + 1
if pkq >= 0:
    print("Tìm thấy mẫu", p, "tại vị trí", pkq)
else:
    print("Không tìm thấy mẫu", p)

Kết quả: Tìm thấy mẫu [1, 2, 3] tại vị trí 3

IV. Sơ đồ tư duy

1. Toán tử in

  • 1.1. Kiểm tra phần tử: <giá_trị> in <danh_sách> → True / False
  • 1.2. So sánh chính xác cả giá trị lẫn kiểu dữ liệu (1 ≠ “1”)
  • 1.3. Duyệt nhanh: for k in A: — biến k nhận trực tiếp giá trị phần tử
  • 1.4. Không cần range() khi chỉ cần đọc giá trị

2. Các phương thức danh sách

  • 2.1. A.append(x) — thêm x vào cuối, độ dài +1
  • 2.2. A.insert(k, x) — chèn x tại vị trí k, đẩy phần tử sang phải, độ dài +1
    • k < 0 → chèn vào đầu; k > len(A) → chèn vào cuối
  • 2.3. A.remove(x) — xóa phần tử đầu tiên có giá trị x, lỗi nếu không tìm thấy
  • 2.4. A.clear() — xóa toàn bộ, A thành []

3. Kĩ thuật xóa phần tử theo điều kiện

  • 3.1. Dùng while + remove(), không dùng for
  • 3.2. Chỉ tăng biến đếm khi không xóa
  • 3.3. Lí do: xóa phần tử → phần tử sau dồn lên, tăng i sẽ bỏ sót

V. Sai lầm thường gặp

Sai lầm Đính chính
1 in ["0", "1", "01", "10"] trả về True False. Số nguyên 1 khác xâu kí tự "1". Toán tử in so sánh chính xác cả kiểu dữ liệu.
Dùng for để xóa phần tử theo điều kiện Sai. Khi xóa phần tử trong vòng for, chỉ số bị lệch dẫn đến bỏ sót phần tử. Phải dùng while và chỉ tăng biến đếm khi không xóa.
A.remove(10) khi 10 không có trong A → không xảy ra gì Sai. Nếu giá trị không tồn tại, remove() sẽ báo lỗi ValueError. Cần kiểm tra bằng if x in A trước khi gọi remove().
A.insert(k, x) báo lỗi khi k ngoài phạm vi chỉ số Không lỗi. Nếu k < 0 → chèn vào đầu, nếu k > len(A) → chèn vào cuối. Đây là khác biệt quan trọng so với truy cập A[k] (sẽ lỗi nếu k ngoài phạm vi).
append(1)insert(0, 1) luôn cho kết quả giống nhau Chỉ giống khi danh sách rỗng. Khi có phần tử: append(1) thêm vào cuối, insert(0, 1) chèn vào đầu → vị trí khác nhau.
for k in Afor i in range(len(A)) dùng thay thế nhau thoải mái Không hoàn toàn. for k in A chỉ đọc giá trị, không có chỉ số nên không thể thay đổi phần tử hay biết vị trí. Khi cần chỉ số (ví dụ tìm vị trí, thay đổi giá trị) → phải dùng for i in range(len(A)).
Thầy Phạm Thành Danh

Thầy Phạm Thành Danh

(Người kiểm duyệt, ra đề)

Chức vụ: Trưởng ban biên soạn môn Tin Học THPT

Trình độ: Cử nhân Sư phạm Tin học, Chứng chỉ hạng II, Chứng chỉ Tin học, Ngoại ngữ B1

Kinh nghiệm: 8+ năm kinh nghiệm tại Trường THPT Thuận Hóa