Bài 1.11. Cấu trúc dữ liệu tập hợp trong Python (list, dict, tuple)

Chào mừng các bạn đến với thế giới của các cấu trúc dữ liệu tập hợp trong Python! Khi lập trình, chúng ta không chỉ làm việc với các biến đơn lẻ mà thường xuyên phải xử lý nhiều dữ liệu cùng lúc. Python cung cấp cho chúng ta những công cụ mạnh mẽ để tổ chức và quản lý các bộ sưu tập dữ liệu này, và ba "ngôi sao" sáng giá nhất chính là List, Tuple, và Dictionary (Dict).

Trong bài này, chúng ta sẽ cùng nhau "mổ xẻ" từng loại cấu trúc dữ liệu này, tìm hiểu đặc điểm, cách sử dụng và khi nào nên chọn loại nào cho phù hợp. Hãy chuẩn bị sẵn sàng để khám phá nhé!

1. List - Gã khổng lồ linh hoạt

List (danh sách) có lẽ là cấu trúc dữ liệu tập hợp được sử dụng phổ biến nhất trong Python. Hãy tưởng tượng nó như một chiếc hộp thần kỳ có thể chứa nhiều loại đồ vật khác nhau (số, chuỗi, thậm chí cả list khác!) và bạn có thể thêm, bớt, thay đổi các món đồ bên trong một cách dễ dàng.

Đặc điểm chính của List:

  • Mutable (Khả biến): Đây là đặc điểm quan trọng nhất! Bạn có thể thay đổi nội dung của list sau khi đã tạo ra nó (thêm, xóa, sửa phần tử).
  • Ordered (Có thứ tự): Các phần tử trong list được lưu trữ theo một thứ tự xác định. Thứ tự này sẽ không thay đổi trừ khi bạn chủ động sắp xếp lại.
  • Indexed (Được đánh chỉ số): Mỗi phần tử trong list có một vị trí (chỉ số - index) duy nhất, bắt đầu từ 0 cho phần tử đầu tiên. Bạn có thể truy cập từng phần tử thông qua chỉ số của nó.
  • Cho phép phần tử trùng lặp: Một list có thể chứa nhiều phần tử có cùng giá trị.
Tạo một List

Có hai cách phổ biến để tạo list:

  • Sử dụng cặp dấu ngoặc vuông [] và liệt kê các phần tử bên trong, cách nhau bởi dấu phẩy.
  • Sử dụng hàm dựng list().
# Cách 1: Dùng dấu ngoặc vuông []
danh_sach_trai_cay = ["táo", "chuối", "cam", "táo"]
danh_sach_so = [1, 5, 2, 8, 3]
danh_sach_hon_hop = [10, "Python", 3.14, True, ["a", "b"]]

print(danh_sach_trai_cay)
print(danh_sach_so)
print(danh_sach_hon_hop)

# Cách 2: Dùng hàm list() - thường dùng để chuyển đổi từ cấu trúc khác
chuoi_ky_tu = "hello"
list_tu_chuoi = list(chuoi_ky_tu)
print(list_tu_chuoi)

Giải thích code:

  • danh_sach_trai_cay, danh_sach_so, danh_sach_hon_hop là các ví dụ tạo list bằng [], chứa các kiểu dữ liệu khác nhau và cả list lồng nhau. danh_sach_trai_cay có phần tử "táo" lặp lại.
  • list(chuoi_ky_tu) chuyển đổi chuỗi hello thành một list các ký tự: ['h', 'e', 'l', 'l', 'o'].
Truy cập phần tử trong List

Bạn sử dụng chỉ số (index) bên trong cặp dấu ngoặc vuông [] để lấy ra phần tử mong muốn. Nhớ rằng chỉ số bắt đầu từ 0.

danh_sach_trai_cay = ["táo", "chuối", "cam", "dâu"]

# Lấy phần tử đầu tiên (chỉ số 0)
phan_tu_dau = danh_sach_trai_cay[0]
print(f"Phần tử đầu tiên: {phan_tu_dau}") # Output: Phần tử đầu tiên: táo

# Lấy phần tử thứ ba (chỉ số 2)
phan_tu_thu_ba = danh_sach_trai_cay[2]
print(f"Phần tử thứ ba: {phan_tu_thu_ba}") # Output: Phần tử thứ ba: cam

# Chỉ số âm: Truy cập từ cuối danh sách (-1 là phần tử cuối cùng)
phan_tu_cuoi = danh_sach_trai_cay[-1]
print(f"Phần tử cuối cùng: {phan_tu_cuoi}") # Output: Phần tử cuối cùng: dâu

# Slicing (cắt lát): Lấy một đoạn của list [start:stop:step]
# Lưu ý: stop là chỉ số *sau* phần tử cuối cùng muốn lấy
doan_list = danh_sach_trai_cay[1:3] # Lấy từ chỉ số 1 đến *trước* chỉ số 3
print(f"Đoạn list từ index 1 đến 2: {doan_list}") # Output: ['chuối', 'cam']

# Lấy từ đầu đến trước chỉ số 2
doan_list_dau = danh_sach_trai_cay[:2]
print(f"Đoạn list đầu: {doan_list_dau}") # Output: ['táo', 'chuối']

# Lấy từ chỉ số 1 đến hết
doan_list_cuoi = danh_sach_trai_cay[1:]
print(f"Đoạn list cuối: {doan_list_cuoi}") # Output: ['chuối', 'cam', 'dâu']

Giải thích code:

  • danh_sach_trai_cay[0] lấy phần tử ở vị trí đầu tiên.
  • danh_sach_trai_cay[-1] là cách tiện lợi để lấy phần tử cuối cùng.
  • danh_sach_trai_cay[1:3] tạo ra một list mới chứa các phần tử từ chỉ số 1 (chuối) đến chỉ số 2 (cam). Phần tử ở chỉ số 3 (dâu) không được bao gồm.
Thay đổi phần tử trong List

Vì list là mutable, bạn có thể dễ dàng thay đổi giá trị của một phần tử bằng cách gán giá trị mới thông qua chỉ số của nó.

danh_sach_mau = ["đỏ", "xanh", "vàng"]
print(f"List ban đầu: {danh_sach_mau}")

# Thay đổi phần tử thứ hai (chỉ số 1)
danh_sach_mau[1] = "tím"
print(f"List sau khi thay đổi: {danh_sach_mau}") # Output: ['đỏ', 'tím', 'vàng']

# Thay đổi một đoạn list bằng slicing
danh_sach_mau[0:2] = ["cam", "lục"] # Thay thế "đỏ", "tím" bằng "cam", "lục"
print(f"List sau khi thay đổi đoạn: {danh_sach_mau}") # Output: ['cam', 'lục', 'vàng']

Giải thích code:

  • danh_sach_mau[1] = "tím" gán trực tiếp giá trị mới "tím" cho phần tử tại chỉ số 1, thay thế "xanh".
  • danh_sach_mau[0:2] = ["cam", "lục"] thay thế các phần tử từ chỉ số 0 đến trước 2 bằng các phần tử trong list ["cam", "lục"].
Thêm phần tử vào List

Có nhiều cách để thêm phần tử:

  • append(item): Thêm một phần tử vào cuối list.
  • insert(index, item): Chèn một phần tử vào vị trí index cụ thể.
  • extend(iterable): Nối thêm tất cả các phần tử từ một đối tượng có thể duyệt được (như list khác, tuple, chuỗi) vào cuối list hiện tại.
numbers = [1, 2, 3]
print(f"List ban đầu: {numbers}")

# Thêm số 4 vào cuối list
numbers.append(4)
print(f"Sau khi append(4): {numbers}") # Output: [1, 2, 3, 4]

# Chèn số 0 vào đầu list (chỉ số 0)
numbers.insert(0, 0)
print(f"Sau khi insert(0, 0): {numbers}") # Output: [0, 1, 2, 3, 4]

# Chèn số 1.5 vào vị trí chỉ số 2
numbers.insert(2, 1.5)
print(f"Sau khi insert(2, 1.5): {numbers}") # Output: [0, 1, 1.5, 2, 3, 4]

# Nối thêm một list khác vào cuối
more_numbers = [5, 6]
numbers.extend(more_numbers)
print(f"Sau khi extend([5, 6]): {numbers}") # Output: [0, 1, 1.5, 2, 3, 4, 5, 6]

# Lưu ý sự khác biệt giữa append và extend khi thêm list
numbers.append([7, 8]) # Thêm list [7, 8] như *một* phần tử duy nhất
print(f"Sau khi append([7, 8]): {numbers}") # Output: [0, 1, 1.5, 2, 3, 4, 5, 6, [7, 8]]

Giải thích code:

  • append() luôn thêm vào cuối và chỉ thêm một phần tử.
  • insert() cho phép bạn chọn vị trí chèn. Các phần tử phía sau vị trí chèn sẽ bị đẩy lùi.
  • extend() "gộp" các phần tử của iterable được cung cấp vào list gốc. Nó khác với append() khi đối số là một list khác.
Xóa phần tử khỏi List

Bạn có thể xóa phần tử bằng các cách sau:

  • remove(value): Xóa phần tử đầu tiên trong list có giá trị value. Gây lỗi ValueError nếu không tìm thấy giá trị.
  • pop([index]): Xóa và trả về phần tử tại vị trí index. Nếu không có index, nó xóa và trả về phần tử cuối cùng. Gây lỗi IndexError nếu index không hợp lệ.
  • del list_name[index]: Xóa phần tử tại vị trí index.
  • del list_name[start:stop]: Xóa một đoạn (slice) của list.
  • clear(): Xóa tất cả các phần tử khỏi list, làm cho list trở nên rỗng.
letters = ['a', 'b', 'c', 'd', 'b', 'e']
print(f"List ban đầu: {letters}")

# Xóa chữ 'b' đầu tiên tìm thấy
letters.remove('b')
print(f"Sau khi remove('b'): {letters}") # Output: ['a', 'c', 'd', 'b', 'e']

# Xóa và lấy phần tử cuối cùng
last_item = letters.pop()
print(f"Phần tử bị xóa bằng pop(): {last_item}") # Output: e
print(f"List sau khi pop(): {letters}") # Output: ['a', 'c', 'd', 'b']

# Xóa và lấy phần tử tại chỉ số 1 ('c')
item_at_index_1 = letters.pop(1)
print(f"Phần tử bị xóa bằng pop(1): {item_at_index_1}") # Output: c
print(f"List sau khi pop(1): {letters}") # Output: ['a', 'd', 'b']

# Dùng del để xóa phần tử tại chỉ số 0 ('a')
del letters[0]
print(f"List sau khi del letters[0]: {letters}") # Output: ['d', 'b']

# Dùng del để xóa một đoạn (ở đây chỉ còn 1 phần tử 'b' từ index 1 đến hết)
letters_copy = ['x', 'y', 'z', 'w']
del letters_copy[1:3] # Xóa 'y' và 'z'
print(f"List copy sau khi del slice [1:3]: {letters_copy}") # Output: ['x', 'w']

# Xóa sạch list letters
letters.clear()
print(f"List letters sau khi clear(): {letters}") # Output: []

Giải thích code:

  • remove() tìm theo giá trị, chỉ xóa lần xuất hiện đầu tiên.
  • pop() tìm theo chỉ số (mặc định là cuối cùng) và trả về giá trị đã xóa.
  • del là một câu lệnh Python (không phải phương thức của list) dùng để xóa đối tượng, bao gồm cả phần tử hoặc lát cắt của list theo chỉ số.
  • clear() làm rỗng list.
Các thao tác hữu ích khác với List
  • len(list_name): Trả về số lượng phần tử trong list.
  • list_name.sort(): Sắp xếp các phần tử trong list tại chỗ (thay đổi list gốc). Mặc định là tăng dần.
  • list_name.sort(reverse=True): Sắp xếp giảm dần.
  • list_name.reverse(): Đảo ngược thứ tự các phần tử tại chỗ.
  • list_name.count(value): Đếm số lần xuất hiện của value trong list.
  • list_name.index(value, [start, [stop]]): Trả về chỉ số của lần xuất hiện đầu tiên của value. Gây lỗi ValueError nếu không tìm thấy. Có thể giới hạn phạm vi tìm kiếm bằng startstop.
  • Toán tử +: Nối hai list để tạo ra một list mới.
  • Toán tử *: Lặp lại list nhiều lần để tạo ra một list mới.
  • Toán tử in: Kiểm tra xem một phần tử có tồn tại trong list hay không (trả về True hoặc False).
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
chars = ['c', 'a', 'b']

# Độ dài list
print(f"Độ dài list numbers: {len(numbers)}") # Output: 8

# Đếm số lần xuất hiện số 1
print(f"Số lần xuất hiện số 1: {numbers.count(1)}") # Output: 2

# Tìm chỉ số của số 9
print(f"Chỉ số của số 9: {numbers.index(9)}") # Output: 5

# Sắp xếp list numbers tăng dần (tại chỗ)
numbers.sort()
print(f"List numbers sau khi sort(): {numbers}") # Output: [1, 1, 2, 3, 4, 5, 6, 9]

# Sắp xếp list chars giảm dần (tại chỗ)
chars.sort(reverse=True)
print(f"List chars sau khi sort(reverse=True): {chars}") # Output: ['c', 'b', 'a']

# Đảo ngược list chars (tại chỗ)
chars.reverse()
print(f"List chars sau khi reverse(): {chars}") # Output: ['a', 'b', 'c']

# Nối list
list1 = [1, 2]
list2 = [3, 4]
list_noi = list1 + list2
print(f"Nối list1 và list2: {list_noi}") # Output: [1, 2, 3, 4]
print(f"List1 gốc không đổi: {list1}") # Output: [1, 2]

# Lặp lại list
list_lap = list1 * 3
print(f"Lặp lại list1 3 lần: {list_lap}") # Output: [1, 2, 1, 2, 1, 2]

# Kiểm tra sự tồn tại
print(f"Số 5 có trong numbers không? {5 in numbers}") # Output: True
print(f"Số 10 có trong numbers không? {10 in numbers}") # Output: False

Giải thích code:

  • Các ví dụ minh họa cách sử dụng len(), count(), index(), sort(), reverse(). Lưu ý sort()reverse() thay đổi list gốc.
  • Toán tử +* tạo ra list mới mà không làm thay đổi các list ban đầu.
  • Toán tử in là cách nhanh chóng để kiểm tra thành viên.

2. Tuple - Người anh em bất biến

Tuple (bộ) rất giống với List, nhưng có một khác biệt cực kỳ quan trọng: tuple là immutable (bất biến). Một khi bạn đã tạo ra một tuple, bạn không thể thay đổi nội dung của nó - không thể thêm, xóa hay sửa đổi các phần tử.

Tại sao lại cần Tuple khi đã có List?

  • An toàn dữ liệu: Vì tuple không thể thay đổi, nó đảm bảo rằng dữ liệu bên trong sẽ không bị vô tình sửa đổi ở đâu đó trong chương trình. Điều này hữu ích cho việc lưu trữ các hằng số hoặc dữ liệu không nên thay đổi.
  • Khóa Dictionary: Các khóa (key) của Dictionary phải là các đối tượng bất biến. Do đó, bạn có thể dùng tuple làm khóa, nhưng không thể dùng list.
  • Tối ưu hóa (tiềm năng): Trong một số trường hợp, Python có thể xử lý tuple hiệu quả hơn một chút so với list do tính bất biến của nó (mặc dù sự khác biệt thường không đáng kể).
  • Ngữ nghĩa: Sử dụng tuple cho thấy rõ ý định của lập trình viên rằng đây là một bộ sưu tập dữ liệu cố định.

Đặc điểm chính của Tuple:

  • Immutable (Bất biến): Không thể thay đổi sau khi tạo.
  • Ordered (Có thứ tự): Giống như list, các phần tử có thứ tự xác định.
  • Indexed (Được đánh chỉ số): Truy cập phần tử bằng chỉ số, bắt đầu từ 0.
  • Cho phép phần tử trùng lặp: Có thể chứa các phần tử giống nhau.
Tạo một Tuple
  • Sử dụng cặp dấu ngoặc đơn () và liệt kê các phần tử, cách nhau bởi dấu phẩy.
  • Có thể bỏ qua dấu ngoặc đơn nếu ngữ cảnh rõ ràng (gọi là tuple packing).
  • Để tạo tuple chỉ có một phần tử, bạn bắt buộc phải thêm dấu phẩy sau phần tử đó.
  • Sử dụng hàm dựng tuple().
# Cách 1: Dùng dấu ngoặc đơn ()
my_tuple = (1, "hello", 3.14)
print(my_tuple)

# Cách 2: Bỏ qua dấu ngoặc đơn (tuple packing)
another_tuple = 10, 20, "world" # Python tự hiểu đây là tuple
print(another_tuple)
print(type(another_tuple)) # Output: <class 'tuple'>

# Cách 3: Tuple với một phần tử (LƯU Ý DẤU PHẨY)
single_element_tuple = (99,) # Dấu phẩy là bắt buộc
not_a_tuple = (99)      # Đây chỉ là số 99 trong ngoặc đơn

print(single_element_tuple)
print(type(single_element_tuple)) # Output: <class 'tuple'>
print(not_a_tuple)
print(type(not_a_tuple))      # Output: <class 'int'>

# Cách 4: Dùng hàm tuple()
list_data = [1, 2, 3]
tuple_from_list = tuple(list_data)
print(tuple_from_list)
print(type(tuple_from_list)) # Output: <class 'tuple'>

Giải thích code:

  • Các ví dụ cho thấy các cách tạo tuple khác nhau.
  • Ví dụ về single_element_tuple nhấn mạnh tầm quan trọng của dấu phẩy , khi tạo tuple một phần tử. Nếu không có dấu phẩy, Python sẽ hiểu đó là giá trị đơn lẻ nằm trong ngoặc đơn ưu tiên tính toán.
Truy cập phần tử trong Tuple

Việc truy cập phần tử trong tuple hoàn toàn giống như với list, sử dụng chỉ số và slicing.

coordinates = (10, 20, 30)

# Lấy phần tử đầu tiên
x = coordinates[0]
print(f"Tọa độ x: {x}") # Output: 10

# Lấy phần tử cuối cùng
z = coordinates[-1]
print(f"Tọa độ z: {z}") # Output: 30

# Slicing tuple
xy = coordinates[0:2]
print(f"Tọa độ xy: {xy}") # Output: (10, 20) - Lưu ý kết quả vẫn là tuple
Tuple là bất biến (Immutable)

Đây là điểm khác biệt cốt lõi. Mọi nỗ lực thay đổi, thêm, hoặc xóa phần tử của tuple sau khi tạo sẽ gây ra lỗi TypeError.

my_tuple = (1, 2, 3)

# Thử thay đổi phần tử
try:
    my_tuple[0] = 100
except TypeError as e:
    print(f"Lỗi khi cố thay đổi tuple: {e}")
    # Output: Lỗi khi cố thay đổi tuple: 'tuple' object does not support item assignment

# Thử thêm phần tử (tuple không có phương thức append, insert, extend)
# my_tuple.append(4) # Sẽ gây AttributeError

# Thử xóa phần tử (tuple không có phương thức remove, pop, clear; del cũng không hoạt động với phần tử)
try:
    del my_tuple[0]
except TypeError as e:
    print(f"Lỗi khi cố xóa phần tử tuple: {e}")
    # Output: Lỗi khi cố xóa phần tử tuple: 'tuple' object doesn't support item deletion

Giải thích code:

  • Các khối try...except bắt lỗi TypeError xảy ra khi cố gắng thực hiện các thao tác thay đổi nội dung của tuple, chứng minh tính bất biến của nó.
Các thao tác với Tuple

Mặc dù không thể thay đổi tuple, bạn vẫn có thể thực hiện một số thao tác tương tự list không làm thay đổi tuple gốc:

  • len(tuple_name): Lấy số lượng phần tử.
  • tuple_name.count(value): Đếm số lần xuất hiện của value.
  • tuple_name.index(value, [start, [stop]]): Tìm chỉ số của value.
  • Toán tử +: Nối hai tuple để tạo ra một tuple mới.
  • Toán tử *: Lặp lại tuple để tạo ra một tuple mới.
  • Toán tử in: Kiểm tra sự tồn tại của phần tử.
  • Tuple Unpacking: Gán các phần tử của tuple vào các biến riêng lẻ.
t = (1, 2, 2, 3, 4, 2)

print(f"Độ dài tuple t: {len(t)}") # Output: 6
print(f"Số lần xuất hiện số 2: {t.count(2)}") # Output: 3
print(f"Chỉ số đầu tiên của số 3: {t.index(3)}") # Output: 3

t1 = (1, 2)
t2 = (3, 4)
t_noi = t1 + t2
print(f"Nối t1 và t2: {t_noi}") # Output: (1, 2, 3, 4)

t_lap = t1 * 2
print(f"Lặp lại t1 2 lần: {t_lap}") # Output: (1, 2, 1, 2)

print(f"Số 5 có trong t không? {5 in t}") # Output: False

# Tuple Unpacking
point = (10, 25)
x, y = point # Gán phần tử đầu cho x, phần tử thứ hai cho y
print(f"x = {x}, y = {y}") # Output: x = 10, y = 25

# Số biến phải khớp với số phần tử trong tuple
try:
    a, b = (1, 2, 3)
except ValueError as e:
    print(f"Lỗi unpacking: {e}")
    # Output: Lỗi unpacking: too many values to unpack (expected 2)

Giải thích code:

  • Minh họa các phương thức và toán tử hoạt động trên tuple. Lưu ý +* tạo tuple mới.
  • Tuple Unpacking là một tính năng rất hữu ích, cho phép gán giá trị từ tuple (hoặc list) vào các biến một cách gọn gàng. Số lượng biến bên trái dấu = phải bằng số lượng phần tử trong tuple bên phải.

3. Dictionary (Dict) - Sức mạnh của Key-Value

Dictionary (từ điển), thường được gọi tắt là Dict, là một cấu trúc dữ liệu cực kỳ linh hoạt và mạnh mẽ để lưu trữ dữ liệu dưới dạng cặp key-value (khóa-giá trị). Hãy tưởng tượng nó như một cuốn từ điển thực sự: bạn tra cứu một từ (key) để tìm nghĩa của nó (value).

Đặc điểm chính của Dictionary:

  • Mutable (Khả biến): Bạn có thể thêm, xóa, và cập nhật các cặp key-value sau khi dict đã được tạo.
  • Unordered (Không có thứ tự) trước Python 3.7 / Ordered (Có thứ tự) từ Python 3.7 trở đi:
    • Trong các phiên bản Python trước 3.7, các cặp key-value trong dict không được đảm bảo giữ nguyên thứ tự khi bạn thêm chúng vào.
    • Từ Python 3.7 trở đi, dict ghi nhớ thứ tự các cặp key-value được thêm vào. Đây là một thay đổi quan trọng và rất hữu ích.
  • Key-based (Dựa trên khóa): Bạn truy cập giá trị (value) thông qua khóa (key) của nó, chứ không phải qua chỉ số (index) như list hay tuple.
  • Khóa (Key) phải là duy nhất và bất biến:
    • Mỗi khóa trong một dictionary phải là duy nhất. Nếu bạn cố gắng thêm một cặp với khóa đã tồn tại, giá trị cũ sẽ bị ghi đè.
    • Khóa phải thuộc kiểu dữ liệu bất biến (immutable) như số (int, float), chuỗi (string), hoặc tuple (nếu tuple đó chỉ chứa các phần tử bất biến). Bạn không thể dùng list hoặc dict khác làm khóa.
  • Giá trị (Value) có thể là bất kỳ kiểu dữ liệu nào: Giá trị có thể là số, chuỗi, list, tuple, dict khác, hàm, đối tượng,... và có thể trùng lặp.
Tạo một Dictionary
  • Sử dụng cặp dấu ngoặc nhọn {} với các cặp key: value, cách nhau bởi dấu phẩy.
  • Sử dụng hàm dựng dict().
# Cách 1: Dùng dấu ngoặc nhọn {}
sinh_vien = {
    "ten": "Nguyễn Văn A",
    "ma_sv": "SV001",
    "tuoi": 20,
    "diem_tb": 8.5,
    "qua_mon": True
}
print(sinh_vien)

# Dict rỗng
dict_rong = {}
print(dict_rong)

# Cách 2: Dùng hàm dict() với các đối số keyword
thong_tin = dict(ten="Sản phẩm X", gia=150.0, ton_kho=50)
print(thong_tin)

# Cách 3: Dùng hàm dict() với list/tuple các cặp key-value
cap_key_value = [("mau", "xanh"), ("kich_thuoc", "L")]
dict_tu_list = dict(cap_key_value)
print(dict_tu_list)

Giải thích code:

  • sinh_vien là ví dụ điển hình của dict, lưu trữ thông tin có cấu trúc. Khóa là chuỗi, giá trị có nhiều kiểu khác nhau.
  • Hàm dict() có thể nhận đối số dạng keyword=value hoặc một iterable chứa các cặp (key, value).
Truy cập Giá trị trong Dictionary

Bạn truy cập giá trị bằng cách cung cấp khóa tương ứng trong cặp dấu ngoặc vuông [] hoặc sử dụng phương thức get().

  • dict_name[key]: Trả về giá trị của key. Gây lỗi KeyError nếu key không tồn tại.
  • dict_name.get(key, [default]): Trả về giá trị của key. Nếu key không tồn tại, nó trả về None (hoặc giá trị default nếu được cung cấp) thay vì gây lỗi.
sinh_vien = {
    "ten": "Nguyễn Văn A",
    "ma_sv": "SV001",
    "tuoi": 20
}

# Truy cập bằng dấu ngoặc vuông []
ten_sv = sinh_vien["ten"]
print(f"Tên sinh viên: {ten_sv}") # Output: Nguyễn Văn A

# Truy cập bằng get()
tuoi_sv = sinh_vien.get("tuoi")
print(f"Tuổi sinh viên: {tuoi_sv}") # Output: 20

# Truy cập khóa không tồn tại bằng [] -> Lỗi
try:
    lop = sinh_vien["lop"]
except KeyError as e:
    print(f"Lỗi khi truy cập khóa 'lop' bằng []: {e}")
    # Output: Lỗi khi truy cập khóa 'lop' bằng []: 'lop'

# Truy cập khóa không tồn tại bằng get() -> Trả về None
lop = sinh_vien.get("lop")
print(f"Lớp (dùng get()): {lop}") # Output: None

# Truy cập khóa không tồn tại bằng get() với giá trị default
lop_default = sinh_vien.get("lop", "Chưa có thông tin")
print(f"Lớp (dùng get() với default): {lop_default}") # Output: Chưa có thông tin

Giải thích code:

  • Ví dụ cho thấy sự khác biệt quan trọng giữa truy cập bằng [] (gây lỗi nếu key không tồn tại) và get() (an toàn hơn, trả về None hoặc giá trị mặc định).
Thêm và Cập nhật phần tử trong Dictionary

Bạn có thể thêm cặp key-value mới hoặc cập nhật giá trị của khóa đã tồn tại bằng cách gán giá trị thông qua khóa.

  • dict_name[new_key] = value: Nếu new_key chưa có, thêm cặp mới.
  • dict_name[existing_key] = new_value: Nếu existing_key đã có, cập nhật giá trị của nó.
  • dict_name.update(other_dict): Cập nhật dict với các cặp key-value từ một dict khác (hoặc iterable chứa cặp key-value). Các khóa trùng lặp sẽ bị ghi đè bởi giá trị từ other_dict.
sinh_vien = {"ten": "Nguyễn Văn A", "tuoi": 20}
print(f"Dict ban đầu: {sinh_vien}")

# Thêm cặp key-value mới
sinh_vien["ma_sv"] = "SV001"
print(f"Sau khi thêm 'ma_sv': {sinh_vien}")
# Output: {'ten': 'Nguyễn Văn A', 'tuoi': 20, 'ma_sv': 'SV001'}

# Cập nhật giá trị của key đã tồn tại
sinh_vien["tuoi"] = 21
print(f"Sau khi cập nhật 'tuoi': {sinh_vien}")
# Output: {'ten': 'Nguyễn Văn A', 'tuoi': 21, 'ma_sv': 'SV001'}

# Sử dụng update để thêm/cập nhật nhiều cặp
thong_tin_bo_sung = {"diem_tb": 8.0, "lop": "K60", "tuoi": 22} # 'tuoi' sẽ ghi đè
sinh_vien.update(thong_tin_bo_sung)
print(f"Sau khi update: {sinh_vien}")
# Output: {'ten': 'Nguyễn Văn A', 'tuoi': 22, 'ma_sv': 'SV001', 'diem_tb': 8.0, 'lop': 'K60'}

Giải thích code:

  • Gán giá trị qua [] là cách phổ biến nhất để thêm/sửa.
  • update() rất tiện lợi khi bạn muốn gộp thông tin từ một dict khác vào dict hiện tại.
Xóa phần tử khỏi Dictionary
  • pop(key, [default]): Xóa cặp key-value tương ứng với keytrả về giá trị của nó. Gây lỗi KeyError nếu key không tồn tại và không có default. Nếu có default, nó sẽ trả về default thay vì gây lỗi.
  • popitem(): Xóa và trả về cặp key-value cuối cùng được thêm vào (trong Python 3.7+) hoặc một cặp key-value tùy ý (trước Python 3.7). Gây lỗi KeyError nếu dict rỗng.
  • del dict_name[key]: Xóa cặp key-value tương ứng với key. Gây lỗi KeyError nếu key không tồn tại.
  • clear(): Xóa tất cả các cặp key-value khỏi dict, làm cho dict trở nên rỗng.
config = {"host": "localhost", "port": 8080, "debug": True, "user": "admin"}
print(f"Dict ban đầu: {config}")

# Xóa và lấy giá trị của 'port'
port_value = config.pop("port")
print(f"Giá trị của 'port' đã xóa: {port_value}") # Output: 8080
print(f"Dict sau khi pop('port'): {config}")
# Output: {'host': 'localhost', 'debug': True, 'user': 'admin'}

# Xóa key không tồn tại với giá trị default
db_name = config.pop("database", None) # Không gây lỗi, trả về None
print(f"Giá trị của 'database' (không tồn tại): {db_name}") # Output: None
print(f"Dict không đổi: {config}")

# Xóa và lấy cặp key-value cuối cùng (trong Python 3.7+)
last_item = config.popitem()
print(f"Cặp key-value cuối cùng đã xóa: {last_item}") # Output: ('user', 'admin')
print(f"Dict sau khi popitem(): {config}")
# Output: {'host': 'localhost', 'debug': True}

# Dùng del để xóa 'debug'
del config["debug"]
print(f"Dict sau khi del config['debug']: {config}")
# Output: {'host': 'localhost'}

# Xóa sạch dict
config.clear()
print(f"Dict sau khi clear(): {config}") # Output: {}

Giải thích code:

  • pop() xóa theo key và trả về value. Có thể cung cấp giá trị default để tránh lỗi.
  • popitem() hữu ích khi bạn muốn xử lý và xóa cặp cuối cùng (hoặc một cặp bất kỳ trong phiên bản cũ).
  • del xóa theo key nhưng không trả về giá trị.
  • clear() làm rỗng dict.
Duyệt qua Dictionary

Bạn có thể duyệt qua các khóa, các giá trị, hoặc cả các cặp key-value của dictionary.

  • dict_name.keys(): Trả về một đối tượng view chứa tất cả các khóa.
  • dict_name.values(): Trả về một đối tượng view chứa tất cả các giá trị.
  • dict_name.items(): Trả về một đối tượng view chứa tất cả các cặp (key, value) dưới dạng tuple.
sinh_vien = {"ten": "Nguyễn Văn A", "tuoi": 21, "ma_sv": "SV001"}

# Duyệt qua các khóa (cách mặc định khi duyệt dict)
print("--- Duyệt qua khóa ---")
for key in sinh_vien: # Hoặc for key in sinh_vien.keys():
    print(key)

# Duyệt qua các giá trị
print("\n--- Duyệt qua giá trị ---")
for value in sinh_vien.values():
    print(value)

# Duyệt qua các cặp key-value
print("\n--- Duyệt qua cặp key-value ---")
for key, value in sinh_vien.items():
    print(f"Khóa: {key}, Giá trị: {value}")

Giải thích code:

  • Duyệt trực tiếp for key in sinh_vien là cách phổ biến để lấy các khóa.
  • .values() dùng để lấy riêng các giá trị.
  • .items() rất tiện lợi khi bạn cần cả khóa và giá trị trong vòng lặp. Nó trả về các tuple (key, value) mà bạn có thể unpacking trực tiếp trong for.
Các thao tác hữu ích khác với Dictionary
  • len(dict_name): Trả về số lượng cặp key-value.
  • Toán tử in: Kiểm tra xem một khóa có tồn tại trong dictionary hay không.
  • dict_name.copy(): Tạo một bản sao nông (shallow copy) của dictionary.
scores = {"Alice": 90, "Bob": 85, "Charlie": 95}

print(f"Số lượng sinh viên: {len(scores)}") # Output: 3

# Kiểm tra sự tồn tại của khóa
print(f"'Bob' có trong scores không? {'Bob' in scores}") # Output: True
print(f"'David' có trong scores không? {'David' in scores}") # Output: False

# Lưu ý: 'in' kiểm tra KHÓA, không phải giá trị
print(f"Giá trị 90 có trong scores không? {90 in scores}") # Output: False
# Để kiểm tra giá trị, dùng .values()
print(f"Giá trị 90 có trong scores.values() không? {90 in scores.values()}") # Output: True

# Tạo bản sao
scores_copy = scores.copy()
scores_copy["Alice"] = 100 # Thay đổi bản sao

print(f"Scores gốc: {scores}") # Output: {'Alice': 90, 'Bob': 85, 'Charlie': 95} (không đổi)
print(f"Scores bản sao: {scores_copy}") # Output: {'Alice': 100, 'Bob': 85, 'Charlie': 95}

Giải thích code:

  • len() cho số cặp key-value.
  • in là cách hiệu quả để kiểm tra sự tồn tại của khóa. Muốn kiểm tra giá trị, bạn phải dùng in dict_name.values().
  • .copy() tạo ra một dict mới. Thay đổi trên bản sao không ảnh hưởng đến bản gốc (đối với shallow copy, nếu giá trị là các đối tượng mutable như list, thì thay đổi bên trong list đó sẽ ảnh hưởng cả hai).

4. Khi nào nên dùng List, Tuple, hay Dict?

Việc lựa chọn cấu trúc dữ liệu phù hợp là rất quan trọng để viết code hiệu quả và dễ hiểu. Dưới đây là hướng dẫn nhanh:

  • Dùng List khi:

    • Bạn cần một bộ sưu tập các mục có thứ tự.
    • Bộ sưu tập có thể thay đổi (thêm, xóa, sửa phần tử).
    • Bạn thường xuyên truy cập phần tử thông qua vị trí (chỉ số) của chúng.
    • Ví dụ: danh sách sinh viên trong lớp, danh sách các bước cần làm, lịch sử duyệt web.
  • Dùng Tuple khi:

    • Bạn cần một bộ sưu tập các mục có thứ tự nhưng không được thay đổi sau khi tạo (bất biến).
    • Bạn muốn đảm bảo dữ liệu không bị vô tình sửa đổi.
    • Bạn cần dùng bộ sưu tập làm khóa cho dictionary.
    • Ví dụ: tọa độ (x, y), bộ ba màu RGB (red, green, blue), thông tin cấu hình cố định.
  • Dùng Dictionary khi:

    • Bạn cần lưu trữ dữ liệu dưới dạng key-value.
    • Bạn cần truy cập dữ liệu nhanh chóng thông qua một khóa định danh duy nhất thay vì vị trí.
    • Thứ tự không quá quan trọng (trước Python 3.7) hoặc bạn cần thứ tự chèn được duy trì (Python 3.7+).
    • Dữ liệu có cấu trúc rõ ràng với các thuộc tính và giá trị tương ứng.
    • Ví dụ: thông tin chi tiết của một người (tên, tuổi, địa chỉ), cấu hình ứng dụng, tần suất xuất hiện của từ trong văn bản.

Hiểu rõ về List, Tuple, và Dictionary là nền tảng vững chắc để bạn làm việc hiệu quả hơn với Python. Chúng là những công cụ không thể thiếu trong "hộp đồ nghề" của bất kỳ lập trình viên Python nào!

Comments

There are no comments at the moment.