Chuyển tới nội dung chính

Cấu trúc dữ liệu

Python có 4 cấu trúc dữ liệu built-in quan trọng: List, Tuple, Dict và Set. Mỗi loại có đặc điểm riêng và phù hợp cho từng bài toán khác nhau. Chọn đúng cấu trúc dữ liệu giúp code hiệu quả và rõ ràng hơn.

List

List là cấu trúc dữ liệu linh hoạt nhất — có thứ tự, có thể thay đổi, cho phép phần tử trùng lặp.

Tạo và truy cập

# Tạo list
scores = [85, 92, 78, 88, 71]
students = ["An", "Bình", "Cường"]
mixed = [1, "hello", True, 3.14, None] # List có thể chứa nhiều kiểu
empty = []
from_range = list(range(1, 6)) # [1, 2, 3, 4, 5]

# Truy cập theo index (bắt đầu từ 0)
print(scores[0]) # Output: 85 — phần tử đầu
print(scores[-1]) # Output: 71 — phần tử cuối
print(scores[-2]) # Output: 88 — phần tử thứ 2 từ cuối

# Slicing: list[start:stop:step]
print(scores[1:4]) # Output: [92, 78, 88] — index 1, 2, 3
print(scores[:3]) # Output: [85, 92, 78] — 3 phần tử đầu
print(scores[2:]) # Output: [78, 88, 71] — từ index 2 đến hết
print(scores[::-1]) # Output: [71, 88, 78, 92, 85] — đảo ngược

Thêm và xóa phần tử

cart = ["táo", "chuối"]

# Thêm
cart.append("xoài") # Thêm vào cuối: ["táo", "chuối", "xoài"]
cart.insert(1, "cam") # Thêm vào vị trí 1: ["táo", "cam", "chuối", "xoài"]
cart.extend(["nho", "lê"]) # Thêm nhiều phần tử: [..., "nho", "lê"]
cart += ["dâu"] # Tương đương extend

print(cart)
# Output: ['táo', 'cam', 'chuối', 'xoài', 'nho', 'lê', 'dâu']

# Xóa
cart.remove("cam") # Xóa phần tử đầu tiên có giá trị "cam"
popped = cart.pop() # Xóa và trả về phần tử cuối
popped_at = cart.pop(0) # Xóa và trả về phần tử tại index 0
del cart[1] # Xóa phần tử tại index 1

print(f"Đã lấy ra: {popped}") # Output: Đã lấy ra: dâu

Tìm kiếm và kiểm tra

scores = [85, 92, 78, 88, 71, 92]

print(85 in scores) # Output: True
print(100 in scores) # Output: False
print(scores.index(92)) # Output: 1 — index đầu tiên của 92
print(scores.count(92)) # Output: 2 — 92 xuất hiện 2 lần
print(len(scores)) # Output: 6

Sắp xếp

scores = [85, 92, 78, 88, 71]

# sort() — sắp xếp IN-PLACE (thay đổi list gốc)
scores.sort()
print(scores) # Output: [71, 78, 85, 88, 92]

scores.sort(reverse=True)
print(scores) # Output: [92, 88, 85, 78, 71]

# sorted() — trả về list MỚI, không thay đổi list gốc
original = [85, 92, 78, 88, 71]
sorted_scores = sorted(original)
print(original) # Output: [85, 92, 78, 88, 71] — không đổi
print(sorted_scores) # Output: [71, 78, 85, 88, 92]

# Sắp xếp theo tiêu chí tùy chọn
students = [
{"name": "An", "score": 85},
{"name": "Bình", "score": 92},
{"name": "Cường", "score": 78},
]
students.sort(key=lambda s: s["score"], reverse=True)
for s in students:
print(f"{s['name']}: {s['score']}")
# Output:
# Bình: 92
# An: 85
# Cường: 78

Các thao tác khác

scores = [85, 92, 78]

scores.reverse() # Đảo ngược IN-PLACE: [78, 92, 85]
scores_copy = scores.copy() # Tạo bản sao shallow
scores.clear() # Xóa hết phần tử: []

# Thống kê nhanh
nums = [4, 2, 8, 1, 9, 3]
print(min(nums)) # Output: 1
print(max(nums)) # Output: 9
print(sum(nums)) # Output: 27

Tuple

Tuple giống List nhưng immutable — không thể thay đổi sau khi tạo.

# Tạo tuple
point = (3, 5)
rgb = (255, 128, 0)
single = (42,) # Tuple 1 phần tử — cần dấu phẩy!
empty = ()
without_paren = 1, 2, 3 # Packing — cũng là tuple

print(type(point)) # Output: <class 'tuple'>
print(point[0]) # Output: 3
print(point[-1]) # Output: 5

# Không thể thay đổi
point[0] = 10 # TypeError: 'tuple' object does not support item assignment

Tuple unpacking

# Unpacking cơ bản
x, y = (3, 5)
print(x, y) # Output: 3 5

# Swap không cần biến tạm
a, b = 10, 20
a, b = b, a
print(a, b) # Output: 20 10

# Unpacking với *
first, *rest = (1, 2, 3, 4, 5)
print(first) # Output: 1
print(rest) # Output: [2, 3, 4, 5]

*beginning, last = (1, 2, 3, 4, 5)
print(beginning) # Output: [1, 2, 3, 4]
print(last) # Output: 5

first, *middle, last = (1, 2, 3, 4, 5)
print(middle) # Output: [2, 3, 4]

# Unpack khi gọi hàm
def add(a, b, c):
return a + b + c

nums = (1, 2, 3)
print(add(*nums)) # Output: 6 — tương đương add(1, 2, 3)

Khi nào dùng Tuple thay List?

# Tuple cho dữ liệu không đổi — tọa độ, màu, record
SCREEN_SIZE = (1920, 1080) # Hằng số
GPS_LOCATION = (10.7769, 106.7009) # Lat/Long TP.HCM
HTTP_METHODS = ("GET", "POST", "PUT", "DELETE")

# Tuple có thể là key của dict (list không được)
cache = {}
cache[(0, 0)] = "origin" # OK — tuple là hashable
cache[[0, 0]] = "origin" # TypeError — list không hashable

# Tuple nhẹ hơn List trong memory
import sys
lst = [1, 2, 3, 4, 5]
tpl = (1, 2, 3, 4, 5)
print(sys.getsizeof(lst)) # Output: 120 (byte)
print(sys.getsizeof(tpl)) # Output: 80 (byte)

Dict

Dict (dictionary) lưu cặp key-value, không cho phép key trùng lặp.

Tạo và truy cập

# Tạo dict
student = {
"name": "Nguyễn Văn An",
"age": 20,
"score": 8.5,
"subjects": ["Toán", "Lý", "Hóa"],
}

# Truy cập theo key
print(student["name"]) # Output: Nguyễn Văn An
print(student["score"]) # Output: 8.5

# KeyError nếu key không tồn tại
print(student["email"]) # KeyError: 'email'

# get() — an toàn hơn, trả về None (hoặc default) nếu không có
print(student.get("email")) # Output: None
print(student.get("email", "N/A")) # Output: N/A

Thêm, sửa, xóa

student = {"name": "An", "score": 8.5}

# Thêm/sửa
student["email"] = "an@example.com" # Thêm key mới
student["score"] = 9.0 # Sửa giá trị

# Xóa
del student["email"] # Xóa key, lỗi nếu không tồn tại
score = student.pop("score") # Xóa và trả về value
score = student.pop("grade", None) # An toàn với default

# Update nhiều key cùng lúc
student.update({"age": 21, "city": "Hà Nội"})
# Python 3.9+: dùng | operator
# student = student | {"age": 21}

print(student)
# Output: {'name': 'An', 'age': 21, 'city': 'Hà Nội'}

Duyệt dict

scores = {"An": 85, "Bình": 92, "Cường": 78}

# Keys, values, items
print(list(scores.keys())) # Output: ['An', 'Bình', 'Cường']
print(list(scores.values())) # Output: [85, 92, 78]
print(list(scores.items())) # Output: [('An', 85), ('Bình', 92), ('Cường', 78)]

# Duyệt
for name, score in scores.items():
status = "Đạt" if score >= 60 else "Không đạt"
print(f"{name}: {score}{status}")

Dict comprehension

# Tạo dict từ 2 list
names = ["An", "Bình", "Cường"]
scores = [85, 92, 78]
score_dict = {name: score for name, score in zip(names, scores)}
print(score_dict) # Output: {'An': 85, 'Bình': 92, 'Cường': 78}

# Lọc và transform
high_scores = {name: score for name, score in score_dict.items() if score >= 80}
print(high_scores) # Output: {'An': 85, 'Bình': 92}

# Đảo ngược key-value
inverted = {score: name for name, score in score_dict.items()}
print(inverted) # Output: {85: 'An', 92: 'Bình', 78: 'Cường'}

Nested dict

classroom = {
"10A1": {
"teacher": "Nguyễn Văn Nam",
"students": 35,
"avg_score": 8.2,
},
"10A2": {
"teacher": "Trần Thị Lan",
"students": 33,
"avg_score": 7.9,
},
}

# Truy cập nested
print(classroom["10A1"]["teacher"]) # Output: Nguyễn Văn Nam
print(classroom["10A2"]["avg_score"]) # Output: 7.9

# An toàn với nested dict
teacher = classroom.get("10A3", {}).get("teacher", "Chưa phân công")
print(teacher) # Output: Chưa phân công

Set

Set lưu tập hợp các giá trị duy nhất, không có thứ tự.

# Tạo set
fruits = {"táo", "chuối", "xoài"}
unique_scores = {85, 92, 78, 85, 92} # Tự loại duplicate
print(unique_scores) # Output: {85, 78, 92} — thứ tự không đảm bảo

empty_set = set() # Không dùng {} — đó là dict rỗng!
from_list = set([1, 2, 2, 3, 3, 3])
print(from_list) # Output: {1, 2, 3}

# Kiểm tra thành viên — O(1), nhanh hơn list O(n)
print("táo" in fruits) # Output: True
print("cam" in fruits) # Output: False

Thêm và xóa

tags = {"python", "backend", "api"}

tags.add("devops") # Thêm một phần tử
tags.update(["docker", "git"]) # Thêm nhiều phần tử

tags.remove("api") # Xóa — lỗi nếu không có
tags.discard("xml") # Xóa an toàn — không lỗi nếu không có
popped = tags.pop() # Xóa và trả về một phần tử ngẫu nhiên

Phép toán tập hợp

python_devs = {"An", "Bình", "Cường", "Dung"}
java_devs = {"Bình", "Em", "Phúc", "Dung"}

# Union — hợp
all_devs = python_devs | java_devs
# hoặc: python_devs.union(java_devs)
print(all_devs)
# Output: {'An', 'Bình', 'Cường', 'Dung', 'Em', 'Phúc'}

# Intersection — giao (biết cả 2 ngôn ngữ)
both = python_devs & java_devs
# hoặc: python_devs.intersection(java_devs)
print(both)
# Output: {'Bình', 'Dung'}

# Difference — hiệu (chỉ biết Python, không biết Java)
only_python = python_devs - java_devs
# hoặc: python_devs.difference(java_devs)
print(only_python)
# Output: {'An', 'Cường'}

# Symmetric difference — chỉ biết một trong hai
exclusive = python_devs ^ java_devs
print(exclusive)
# Output: {'An', 'Cường', 'Em', 'Phúc'}

# Subset và superset
a = {1, 2}
b = {1, 2, 3, 4}
print(a.issubset(b)) # Output: True — a ⊆ b
print(b.issuperset(a)) # Output: True — b ⊇ a

Bảng so sánh: Khi nào dùng cái nào?

Tiêu chíListTupleDictSet
Có thứ tự?Có (Python 3.7+)Không
Mutable?Không
Phần tử trùng?Cho phépCho phépKey không trùngKhông
Truy cập bằng?IndexIndexKeyKhông truy cập trực tiếp
Tìm kiếmO(n)O(n)O(1)O(1)
Dùng khiDanh sách thay đổiDữ liệu cố địnhKey-value mappingTập hợp unique

Quyết định nhanh

Cần key-value?              → Dict
Cần unique, không quan tâm thứ tự? → Set
Dữ liệu không thay đổi? → Tuple
Còn lại (danh sách thông thường) → List

Bài tập

Bài 1. Cho danh sách giao dịch mua hàng: [("Táo", 15000, 3), ("Chuối", 8000, 5), ("Xoài", 25000, 2), ("Táo", 15000, 1)]. Mỗi tuple là (sản_phẩm, đơn_giá, số_lượng). Tính tổng tiền từng sản phẩm và tổng hóa đơn.

Bài 2. Viết hàm count_words(text: str) -> dict đếm số lần xuất hiện của từng từ trong chuỗi. Ví dụ: count_words("hello world hello python"){"hello": 2, "world": 1, "python": 1}.

Bài 3. Cho 2 list: danh sách đăng ký môn Toán và danh sách đăng ký môn Lý. Dùng set để tìm: học sinh học cả 2 môn, học sinh chỉ học Toán, học sinh học ít nhất 1 môn.

Bài 4. Viết hàm group_by_grade(students: list) -> dict nhận list [{"name": ..., "score": ...}] và trả về dict nhóm theo xếp loại: {"Xuất sắc": [...], "Giỏi": [...], ...}.