Trong bài này
Mai ghi điểm của bốn lượt chơi là 7, 9, 6 và 8. Mai muốn thêm điểm lượt mới và xem lại các lượt đã chơi. Một danh sách giúp Mai giữ các điểm theo đúng thứ tự.
Danh sách giữ các phần tử theo thứ tự
Hãy lưu điểm của bốn lượt rồi xem cả danh sách:
diem = [7, 9, 6, 8]
print(diem)
print(len(diem))[7, 9, 6, 8] 4
Danh sách (list) là một dãy các giá trị có thứ tự. Mỗi giá trị trong đó là một phần tử (element). Hai dấu ngoặc vuông bao quanh các phần tử; dấu phẩy ngăn cách chúng. len(diem) trả về 4, số phần tử trong danh sách.
Danh sách có thể rỗng. Nó cũng có thể chứa chuỗi hoặc nhiều kiểu giá trị, nhưng dùng cùng kiểu thường dễ xử lý hơn:
cho = []
ten = ["An", "Binh", "Chi"]
print(len(cho))
print(ten)0 ['An', 'Binh', 'Chi']
[] tạo một danh sách chưa có phần tử. Dấu nháy bao quanh từng tên vì mỗi tên là một chuỗi.
Thứ tự trong danh sách có ý nghĩa. [7, 9] và [9, 7] có cùng hai giá trị nhưng ghi hai thứ tự khác nhau. Một giá trị cũng có thể xuất hiện nhiều lần: [7, 7] vẫn có hai phần tử. len đếm cả hai vị trí, không chỉ đếm các giá trị khác nhau.
Chỉ số và lát cắt chọn phần tử trong danh sách
Bài 9 đã dùng chỉ số cho chuỗi. Danh sách cũng bắt đầu đánh số từ 0:
diem = [7, 9, 6, 8]
print(diem[0])
print(diem[-1])7 8
diem[0] là 7; diem[-1] là 8. Chỉ số âm đếm từ cuối. Hình sau đánh dấu hai vị trí được chọn:
| Bước | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| diem | 7 | 9 | 6 | 8 |
Danh sách có bốn phần tử chỉ có chỉ số không âm từ 0 đến 3. Truy cập vị trí 4 gây lỗi:
diem = [7, 9, 6, 8]
print(diem[4])Lỗi khi chạy, ở dòng 2
IndexError: list index out of range
Python báo IndexError. Danh sách rỗng cũng không có phần tử đầu hay cuối. Hãy kiểm tra len() trước khi lấy phần tử nếu chưa biết danh sách có rỗng không.
Bạn có thể thay một phần tử bằng phép gán. Khác với chuỗi, danh sách cho phép thay đổi nội dung tại một vị trí:
diem = [7, 9, 6, 8]
diem[1] = 10
print(diem)[7, 10, 6, 8]
Chỉ phần tử ở chỉ số 1 đổi thành 10. Phép gán không tự thêm vị trí mới nếu chỉ số nằm ngoài danh sách.
Một lát cắt (slice) lấy các phần tử từ chỉ số đầu đến trước chỉ số cuối:
diem = [7, 9, 6, 8]
print(diem[1:3])
print(diem[:2])
print(diem[::-1])[9, 6] [7, 9] [8, 6, 9, 7]
[1:3] lấy 9 và 6, không lấy chỉ số 3. [:2] bắt đầu từ đầu; [::-1] tạo danh sách theo thứ tự ngược. Lát cắt tạo một danh sách mới; danh sách ban đầu giữ nguyên.
Trong a[bat_dau:ket_thuc:buoc], bước cho biết khoảng cách giữa các vị trí được chọn. Ví dụ, bước 2 lấy cách một vị trí. Các cận lát cắt vượt đầu hoặc cuối danh sách được điều chỉnh về phạm vi có thể lấy. Điều này khác với truy cập một chỉ số riêng lẻ ngoài phạm vi, vốn báo lỗi.
diem = [7, 9, 6, 8, 10]
print(diem[::2])
print(diem[3:20])[7, 6, 10] [8, 10]
Lát cắt đầu chọn các chỉ số 0, 2 và 4. Lát cắt sau bắt đầu ở chỉ số 3 rồi dừng khi hết danh sách; không có phần tử ở chỉ số 20.
| Bước | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| diem | 7 | 9 | 6 | 8 |
Các phương thức thêm, xóa và tìm phần tử
Một phương thức (method) là thao tác gọi qua dấu chấm sau danh sách. append thêm một phần tử vào cuối:
diem = [7, 9]
diem.append(8)
print(diem)[7, 9, 8]
Danh sách diem thay đổi thành ba phần tử. append(8) không trả về danh sách mới để gán lại.
extend thêm lần lượt các phần tử của danh sách khác. Ngược lại, append thêm cả danh sách đó thành một phần tử:
a = [1, 2]
b = [1, 2]
them = [3, 4]
a.extend(them)
b.append(them)
print(a)
print(b)[1, 2, 3, 4] [1, 2, [3, 4]]
insert nhận vị trí trước, giá trị sau. Nó chèn giá trị trước vị trí đó và đẩy các phần tử phía sau sang phải:
diem = [7, 9, 8]
diem.insert(1, 6)
print(diem)[7, 6, 9, 8]
Điểm 6 đứng ở chỉ số 1, trước điểm 9. Để thêm ở cuối, append diễn đạt ý định rõ hơn.
Sau khi chèn hoặc xóa, chỉ số của những phần tử phía sau có thể thay đổi. Trong ví dụ trên, 9 chuyển từ chỉ số 1 sang 2. Khi dùng lại một chỉ số đã lưu, bạn cần tính đến thay đổi này.
pop xóa và trả về phần tử tại chỉ số được chỉ định. Không đưa chỉ số thì nó lấy phần tử cuối:
diem = [7, 9, 8]
vua_bo = diem.pop()
print(vua_bo)
print(diem)8 [7, 9]
Biến vua_bo nhận 8, còn diem giữ 7 và 9. pop(0) xóa phần tử đầu. Gọi pop() trên danh sách rỗng gây IndexError.
remove xóa lần xuất hiện đầu tiên của một giá trị, không dùng chỉ số:
diem = [7, 9, 7, 8]
diem.remove(7)
print(diem)[9, 7, 8]
Điểm 7 đầu tiên bị xóa; điểm 7 còn lại vẫn ở đó. Nếu giá trị không có mặt, remove gây ValueError:
diem = [7, 9]
diem.remove(8)Lỗi khi chạy, ở dòng 2
ValueError: list.remove(x): x not in list
Bạn có thể kiểm tra 8 in diem trước khi xóa. index tìm chỉ số của lần xuất hiện đầu tiên, còn count đếm số lần xuất hiện:
diem = [7, 9, 7, 8]
print(diem.index(7))
print(diem.count(7))
print(diem.count(10))0 2 0
index(7) trả về 0, không phải 2; count(7) trả về 2. Giá trị vắng mặt có số lần bằng 0, nhưng index của giá trị vắng mặt gây ValueError. Kiểm tra bằng in trước nếu chưa chắc có giá trị đó.
Ví dụ sau chỉ tìm chỉ số khi giá trị thực sự có trong danh sách:
diem = [7, 9, 8]
can_tim = 6
if can_tim in diem:
print(diem.index(can_tim))
else:
print("Khong co")Khong co
Vì 6 vắng mặt, nhánh else chạy. count(6) cũng có thể cho biết có bao nhiêu số 6, nhưng nó không cho vị trí.
Duyệt từng giá trị hoặc cả chỉ số lẫn giá trị
Vòng lặp có thể nhận lần lượt từng phần tử, từ đầu đến cuối:
diem = [7, 9, 6, 8]
so_luot = 0
for x in diem:
if x >= 8:
so_luot += 1
print(so_luot)2
Mỗi lượt, x nhận một điểm. Hai điểm 9 và 8 thỏa điều kiện nên kết quả là 2. Với danh sách rỗng, thân vòng lặp không chạy.
Biến x nhận giá trị của từng phần tử. Nếu chỉ gán lại x, bạn không thay thế phần tử đang nằm trong danh sách. Muốn sửa theo vị trí, cần gán qua chỉ số của danh sách:
diem = [3, 5]
for x in diem:
x = 0
print(diem)
diem[0] = 0
print(diem)[3, 5] [0, 5]
Vòng lặp chỉ đổi tên x trong mỗi lượt nên danh sách vẫn là [3, 5]. Phép gán diem[0] = 0 mới thay phần tử đầu.
Khi cần cả vị trí lẫn giá trị, dùng enumerate:
diem = [7, 9, 6]
for i, x in enumerate(diem):
print(i + 1, x)1 7 2 9 3 6
i lần lượt là 0, 1, 2; x nhận điểm tương ứng. In i + 1 giúp người đọc thấy lượt số 1, 2, 3, trong khi chỉ số vẫn bắt đầu từ 0. Tránh thêm hoặc xóa phần tử ngay trong vòng lặp đang duyệt chính danh sách đó, vì vị trí có thể thay đổi và bị bỏ qua.
Bạn có thể truyền 1 làm đối số thứ hai của enumerate để số đếm bắt đầu từ 1. Số đếm này không thay đổi chỉ số thật của danh sách:
diem = [7, 9, 6]
for so_luot, x in enumerate(diem, 1):
print(so_luot, x)1 7 2 9 3 6
zip ghép lần lượt các phần tử cùng vị trí để duyệt song song. Nó dừng khi danh sách ngắn hơn hết phần tử, nên tên cuối dưới đây không có điểm đi kèm:
ten = ["An", "Binh", "Chi"]
diem = [8, 9]
for ban, x in zip(ten, diem):
print(ban, x)An 8 Binh 9
reversed cho phép duyệt từ cuối về đầu mà không tạo bản sao danh sách như a[::-1]. Danh sách ban đầu giữ nguyên:
diem = [7, 9, 6]
for x in reversed(diem):
print(x)
print(diem)6 9 7 [7, 9, 6]
Đọc và in một dòng gồm nhiều số
Giả sử dòng vào chứa các điểm cách nhau bằng dấu cách:
7 9 6 8
diem = list(map(int, input().split()))
print(diem)[7, 9, 6, 8]
input().split() tách dòng thành các chuỗi. map(int, ...) chuyển từng chuỗi thành số nguyên; list(...) gom các kết quả thành danh sách. Nếu dòng chỉ có khoảng trắng, kết quả là danh sách rỗng. Một phần không phải số nguyên sẽ gây ValueError khi chuyển kiểu.
Để in các phần tử cách nhau bằng dấu cách, đặt * trước danh sách trong print:
diem = [7, 9, 6]
print(*diem)7 9 6
Ở đây * đưa từng phần tử làm một đối số cho print. Kết quả không có ngoặc vuông hay dấu phẩy. Một cách khác là chuyển từng số thành chuỗi rồi nối:
diem = [7, 9, 6]
dong = " ".join(map(str, diem))
print(dong)7 9 6
join cần các chuỗi; map(str, diem) cung cấp chúng. Với danh sách rỗng, cả hai cách trên in một dòng trống. Nếu muốn dấu phẩy thay dấu cách, có thể chọn chuỗi phân cách khác trong join.
Không nhầm print(diem) với print(*diem). Cách đầu hiển thị biểu diễn của cả danh sách, có ngoặc vuông và dấu phẩy. Cách sau đưa từng phần tử vào print, nên hữu ích khi đề bài cần các số cách nhau bằng dấu cách. Hãy chọn cách in theo đúng định dạng được yêu cầu.
Biểu thức tạo danh sách mới từ một danh sách
Mai muốn nhân đôi từng điểm thưởng. Một biểu thức tạo danh sách (list comprehension) có thể mô tả việc đó trong một dòng:
diem = [3, 5, 2]
thuong = [x * 2 for x in diem]
print(thuong)
print(diem)[6, 10, 4] [3, 5, 2]
Python lần lượt lấy từng x trong diem, tính x * 2 rồi đặt kết quả vào thuong. Danh sách đầu không bị sửa. Kết quả là [6, 10, 4].
Bạn có thể thêm điều kiện sau vòng lặp để chỉ giữ những phần tử phù hợp:
diem = [3, 8, 5, 9]
dat = [x for x in diem if x >= 8]
print(dat)[8, 9]
Điều kiện được thử cho từng x; chỉ 8 và 9 được đưa vào danh sách mới. Nếu không có phần tử thỏa mãn, kết quả là []. Phần biểu thức trước for cũng có thể tính giá trị mới, ví dụ x * 2.
Bạn có thể đọc comprehension từ giữa ra hai phía: lần lượt lấy x từ danh sách nguồn, kiểm tra điều kiện ở cuối, rồi tính biểu thức ở đầu cho giá trị được giữ. Nếu thao tác có nhiều bước khó đọc, một vòng for cùng append cũng là lựa chọn rõ ràng.
Phép gán chia sẻ danh sách còn lát cắt tạo bản sao
Hãy xem điều gì xảy ra khi đặt một tên khác cho cùng danh sách:
a = [4, 6]
b = a
b.append(8)
print(a)
print(b)[4, 6, 8] [4, 6, 8]
b = a không tạo danh sách khác. Cả hai tên cùng tham chiếu (reference) đến một danh sách, nên thay đổi qua b cũng thấy qua a.
Để sửa riêng một bản, tạo bản sao nông (shallow copy) bằng lát cắt hoặc list:
a = [4, 6]
b = a[:]
c = list(a)
b[0] = 9
c.append(7)
print(a)
print(b)
print(c)[4, 6] [9, 6] [4, 6, 7]
a, b, c giờ trỏ tới ba danh sách riêng. Thay một số trong b hoặc thêm vào c không thay đổi a. Đây là bản sao nông: nếu phần tử bên trong là đối tượng thay đổi được, đối tượng ấy vẫn có thể được chia sẻ. Bài này chỉ thực hành danh sách một chiều chứa số hoặc chuỗi.
a.copy() là cách thứ ba để tạo bản sao nông. Sửa bản sao không thay phần tử tương ứng trong danh sách ban đầu:
a = [4, 6]
b = a.copy()
b[0] = 9
print(a)
print(b)[4, 6] [9, 6]
Lưu ý sự khác nhau giữa sửa danh sách và gán lại một tên. Với b = a, lệnh b.append(8) sửa danh sách chung. Nhưng b = [8] cho tên b trỏ đến danh sách khác; lệnh đó không xóa dữ liệu mà a đang tham chiếu.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
Lấy a[len(a)] | Chỉ số cuối nhỏ hơn độ dài một đơn vị. | Kiểm tra danh sách không rỗng rồi dùng a[-1]. |
Nghĩ a[1:3] có cả chỉ số 3 | Cận cuối của lát cắt không được lấy. | Chọn cận cuối lớn hơn vị trí cuối cần lấy một đơn vị. |
Gán a = a.append(5) | append sửa danh sách tại chỗ và trả về None. | Gọi a.append(5) riêng. |
Dùng remove(1) để xóa chỉ số 1 | remove nhận giá trị, không nhận chỉ số. | Dùng pop(1) khi cần xóa theo vị trí. |
Gọi index(x) khi x vắng mặt | Phương thức báo ValueError. | Kiểm tra x in a trước. |
Nghĩ b = a là bản sao | Hai tên tham chiếu cùng danh sách. | Dùng b = a[:] hoặc b = list(a). |
Nối số trực tiếp bằng join | join cần các phần tử là chuỗi. | Dùng " ".join(map(str, a)). |
Tóm tắt
[], chỉ số, lát cắt vàlengiúp tạo và đọc danh sách.append,insert,pop,removethay đổi danh sách;index,countgiúp tìm và đếm.- Duyệt bằng
for x in ahoặcfor i, x in enumerate(a). - Dùng
list(map(int, input().split()))để đọc số vàprint(*a)để in chúng. - Comprehension tạo danh sách mới.
b = achia sẻ danh sách, còna[:]vàlist(a)tạo bản sao nông.