DHDataHọc
Lý thuyếtBài 15/305 phút đọc+10 XP

List

Bạn sẽ học được

  • List là dãy có thứ tự và thay đổi được.
  • Truy cập, cắt, gán lại và thay slice.
  • Thêm, xoá, sắp xếp — và khác biệt sort() với sorted().
  • Bí danh (aliasing), sao chép nông và cái bẫy list lồng nhau.
  • Khi nào list chưa phải cấu trúc dữ liệu phù hợp.

Khái niệm cốt lõi

List là dãy có thứ tự, thay đổi được, chứa được nhiều kiểu giá trị. Đây là cấu trúc dữ liệu dùng nhiều nhất trong Python:

code
nums = [3, 1, 2]
nums.append(4)
print(nums)
print(nums[0], nums[-1], nums[1:3])
code
[3, 1, 2, 4]
3 4 [1, 2]
  • append(4) thêm vào cuối, sửa list tại chỗ.
  • nums[0] là phần tử đầu; nums[-1] là phần tử cuối (chỉ số âm đếm từ cuối).
  • nums[1:3] lấy từ chỉ số 1 đến trước 3, tức hai phần tử [1, 2].
  • Slice luôn trả list mới; list gốc không đổi.

List giữ nguyên thứ tự chèn. Truy cập theo chỉ số là O(1), tìm theo giá trị là O(n).

Thêm và xoá

code
nums = [1, 2]
nums.extend([3, 4])
nums.insert(0, 0)
print(nums)
code
[0, 1, 2, 3, 4]
Thao tácViệc nó làmChi phí
append(x)Thêm một phần tử vào cuốiO(1)
extend(iterable)Thêm nhiều phần tửO(k)
insert(i, x)Chèn tại vị trí iO(n)
remove(x)Xoá lần xuất hiện đầu của xO(n)
pop()Lấy và xoá phần tử cuốiO(1)
pop(i)Lấy và xoá tại iO(n)
del nums[i]Xoá theo chỉ sốO(n)
clear()Xoá toàn bộO(n)

insert(0, x) và pop(0) đẩy toàn bộ phần tử phía sau, nên chèn/xoá ở đầu list lớn rất chậm. Nếu bài toán chỉ thêm/xoá ở hai đầu, collections.deque phù hợp hơn.

code
nums = ["a", "b", "c", "b"]
nums.remove("b")
print(nums)
last = nums.pop()
print(last, nums)
code
['a', 'c', 'b']
b ['a', 'c']

remove("b") chỉ xoá lần đầu. Muốn xoá mọi lần, dùng comprehension. remove ném ValueError nếu không thấy; pop ném IndexError nếu list rỗng.

Truy cập và thay giá trị

code
nums = [10, 20, 30]
nums[1] = 99
print(nums)
nums[1:3] = [7, 8, 9]
print(nums)
code
[10, 99, 30]
[10, 7, 8, 9]

Gán slice có thể đổi độ dài list: ba phần tử bị thay bằng ba phần tử mới, hoặc nhiều/ít hơn. Gán chỉ số đơn chỉ thay một vị trí.

Các phép kiểm tra và tiện ích:

code
nums = [3, 1, 2]
print(nums.index(1), nums.count(2), len(nums), 9 in nums, 1 in nums)
code
1 1 3 False True

index ném ValueError nếu không thấy; in an toàn hơn khi chỉ cần biết có hay không. in trên list là O(n) — với list lớn, kiểm tra nhiều lần nên dùng set.

Đảo và bước nhảy bằng slice:

code
a = [1, 2, 3]
print(a[::-1], a[::2])
code
[3, 2, 1] [1, 3]

sort() và sorted()

Đây là cặp dễ nhầm nhất:

code
scores = [85, 92, 78]
print(sorted(scores))
print(scores)
scores.sort(reverse=True)
print(scores)
code
[78, 85, 92]
[85, 92, 78]
[92, 85, 78]
  • sorted(scores) trả list mới, list gốc không đổi.
  • scores.sort() sửa tại chỗ và trả None.

Vì sort() trả None, dòng sau là lỗi kinh điển:

code
scores = [85, 92, 78]
result = scores.sort()
print(result, scores)
code
None [78, 85, 92]

Biến result là None, còn scores đã bị sắp xếp. Đừng gán kết quả của sort(). Với dữ liệu cần giữ nguyên bản gốc — rất thường gặp khi so sánh trước/sau — dùng sorted().

Cả hai nhận key để sắp theo tiêu chí phụ:

code
rows = [("An", 85), ("Bình", 92), ("Chi", 78)]
print(sorted(rows, key=lambda r: r[1], reverse=True))
code
[('Bình', 92), ('An', 85), ('Chi', 78)]

Bí danh và sao chép

Gán list không tạo bản sao — hai tên trỏ cùng một đối tượng:

code
a = [1, 2, 3]
b = a
b.append(4)
print(a)
code
[1, 2, 3, 4]

Sửa b cũng là sửa a. Muốn bản độc lập, phải sao chép rõ ràng:

code
a = [1, 2, 3]
b = a
b.append(4)
c = a.copy()
c.append(5)
print(a, c)
code
[1, 2, 3, 4] [1, 2, 3, 4, 5]

Ba cách sao chép nông tương đương: a.copy(), a[:], list(a).

Nhưng sao chép nông chỉ sao lớp ngoài; list lồng bên trong vẫn dùng chung:

code
a = [[1], [2]]
b = a.copy()
b[0].append(9)
print(a, b)
code
[[1, 9], [2]] [[1, 9], [2]]

b là list mới, nhưng b[0] và a[0] vẫn trỏ cùng list con. Muốn độc lập hoàn toàn, dùng copy.deepcopy(a). Với dữ liệu dạng bảng, lỗi này khiến "bản sao" vẫn bị thay đổi theo bản gốc.

+= khác +

code
a = [1, 2]
b = a
a += [3]
print(a, b)
c = a + [4]
print(a, c)
code
[1, 2, 3] [1, 2, 3]
[1, 2, 3] [1, 2, 3, 4]

+= gọi extend và sửa tại chỗ, nên b thấy thay đổi. + tạo list mới, nên c độc lập. Với tuple hay chuỗi (bất biến), += cũng tạo đối tượng mới — hành vi khác hẳn list. Đây là lý do không nên dùng += khi có nhiều tham chiếu tới cùng list.

Thực hành

  1. Tạo list doanh số, append thêm, in phần tử đầu/cuối và slice giữa.
  2. Dùng insert(0, ...), remove, pop và quan sát kết quả.
  3. So sánh sorted() với sort(), thử gán result = nums.sort().
  4. Tạo b = a, sửa b, kiểm tra a; sau đó làm lại với a.copy().
  5. Tái hiện cái bẫy list lồng nhau, rồi sửa bằng deepcopy.
  6. Sắp xếp danh sách (tên, điểm) theo điểm giảm dần bằng key.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
IndexErrorChỉ số ngoài phạm viKiểm tra len hoặc dùng slice
ValueError: x is not in listremove/index không thấyKiểm tra in trước
Biến thành None sau khi sortx = x.sort()Dùng sorted(x) hoặc bỏ gán
Sửa bản sao, bản gốc cũng đổiGán b = aDùng a.copy()
Bản sao vẫn đổi theo bản gốcSao chép nông list lồngcopy.deepcopy
Chèn đầu list rất chậminsert(0, ...) O(n)Dùng deque hoặc đảo chiều
IndexError sau pop()Không còn phần tửKiểm tra list rỗng trước

Tóm tắt

  • List có thứ tự, thay đổi được; slice trả bản mới.
  • append/pop cuối là O(1); chèn/xoá giữa hoặc đầu là O(n).
  • sorted() trả list mới; sort() sửa tại chỗ và trả None.
  • Gán list là bí danh, không phải sao chép; copy() chỉ nông.
  • += sửa tại chỗ, + tạo list mới.

Dùng ← → để chuyển bài