Bài tập: hàm thống kê
Bạn sẽ học được
- Ghép hàm, tham số, giá trị trả về và module thành một bài hoàn chỉnh.
- Thiết kế hàm thống kê trả dict có cấu trúc.
- Xử lý list rỗng mà không chia cho 0.
- Tách tính toán khỏi định dạng báo cáo.
- Tự kiểm tra bằng bộ ca thử.
Đề bài
Viết hai hàm:
thong_ke(nums)nhận list số, trả dict gồmcount,sum,mean,min,max. TrảNonenếu list rỗng.bao_cao(rows)nhận list(tên, điểm), in từng người và một dòng tổng kết: trung bình, thấp nhất, cao nhất. Nếu không có dữ liệu, trả chuỗi"Chưa có dữ liệu".
Không in trong thong_ke — hàm này chỉ tính và trả kết quả để dùng lại được.
Khái niệm cốt lõi
Bắt đầu bằng hàm thống kê:
def thong_ke(nums):
if not nums:
return None
return {
"count": len(nums),
"sum": sum(nums),
"mean": sum(nums) / len(nums),
"min": min(nums),
"max": max(nums),
}Ba quyết định thiết kế đáng chú ý:
- Trả
Nonekhi rỗng thay vì0hay dict toàn số 0. Người gọi phân biệt được "không có dữ liệu" với "dữ liệu toàn số 0". - Trả dict thay vì tuple: người gọi đọc
stats["mean"]rõ hơnstats[2], và thêm chỉ số mới không phá vỡ thứ tự. - Tính
sum(nums)hai lần trong ví dụ cho dễ đọc. Với list rất lớn, gántotal = sum(nums)một lần rồi dùng lại — tối ưu nhỏ nhưng miễn phí.
Chạy thử:
print(thong_ke([4, 9, 1, 7]))
print(thong_ke([])){'count': 4, 'sum': 21, 'mean': 5.25, 'min': 1, 'max': 9}
NoneKiểm tra bằng tay: tổng 4 + 9 + 1 + 7 = 21, chia 4 được 5.25. min là 1, max là 9. Kết quả khớp.
Tách báo cáo khỏi tính toán
bao_cao chỉ lo định dạng, gọi thong_ke cho phần số:
def bao_cao(rows):
scores = [score for _, score in rows]
stats = thong_ke(scores)
if stats is None:
return "Chưa có dữ liệu"
lines = [f"{name}: {score}" for name, score in rows]
lines.append(
f"TB {stats['mean']:.2f} | thấp nhất {stats['min']} | cao nhất {stats['max']}"
)
return "\n".join(lines)Điểm quan trọng: bao_cao trả chuỗi, không print. Nhờ vậy có thể kiểm thử bằng cách so sánh chuỗi, ghi ra file, hoặc gửi qua API — người gọi quyết định hiển thị thế nào.
rows = [("An", 85), ("Bình", 92), ("Chi", 78), ("Dũng", 60)]
print(bao_cao(rows))
print(bao_cao([]))An: 85
Bình: 92
Chi: 78
Dũng: 60
TB 78.75 | thấp nhất 60 | cao nhất 92
Chưa có dữ liệu78.75 là trung bình của 85, 92, 78, 60: tổng 315, chia 4. Định dạng .2f giữ hai chữ số thập phân; nếu dùng :g sẽ ra 78.75 nhưng với số tròn như 80.0 lại thành 80, không nhất quán trong báo cáo.
Vì sao dùng _ trong comprehension for _, score in rows: tên _ là quy ước cho "không dùng giá trị này". Nó vẫn là biến hợp lệ, nhưng báo cho người đọc biết tên người học không cần cho phép tính.
Thêm median bằng thư viện chuẩn
Trung bình dễ bị lệch bởi giá trị ngoại lai. Median — giá trị giữa — phản ánh phân bố tốt hơn:
import statistics
print(statistics.median([4, 9, 1, 7]))
print(statistics.median([4, 9, 1, 7, 10]))5.5
7Với bốn số, median là trung bình của hai số giữa 4 và 7 → 5.5. Với năm số, median là 7. Đây là lý do báo cáo thực tế thường in cả mean và median: nếu hai số chênh nhiều, dữ liệu có ngoại lai.
Có thể thêm vào dict:
"median": statistics.median(nums)Nhưng hãy cân nhắc: mỗi chỉ số thêm vào là một dòng code và một khái niệm người đọc phải hiểu. Chỉ thêm khi báo cáo thực sự dùng.
Tự kiểm tra
thong_ke([])trả gì? Vì sao không trả dict vớimean = 0?- Vì sao
thong_kekhông nênprintkết quả? bao_caotrả chuỗi thay vì in — lợi ích gì khi kiểm thử?- Với
[85, 92, 78, 60], trung bình là bao nhiêu? Median là bao nhiêu? - Vì sao dùng
{name: score for name, score in rows}có thể mất dữ liệu? - Nếu list có một phần tử,
min,max,meanbằng gì?
Đáp án:
None;0sẽ bị nhầm với dữ liệu thật bằng 0.- Vì hàm chỉ nên tính và trả; in làm kết quả không dùng lại được.
- So sánh chuỗi trực tiếp, không phải bắt stdout; dùng được trong API/file.
- Mean
78.75; median(78 + 85) / 2 = 81.5. - Nếu trùng tên, khóa sau ghi đè khóa trước — dùng list hoặc cộng dồn thay vì dict.
- Cả ba đều bằng chính phần tử đó.
Thực hành thêm
- Thêm
medianvàstdevvàothong_kebằngstatistics. - Viết
loc_dat(rows, nguong=50)trả list người có điểm từ ngưỡng trở lên. - Viết
xep_hang(rows)trả list đã sắp theo điểm giảm dần, dùngsorted(key=...). - Đổi
bao_caođể nhận thêm tham số*, ví dụdon_vi="điểm". - Thêm kiểm tra điểm ngoài 0–100 và bỏ qua kèm cảnh báo.
- Đặt hai hàm vào
helpers.py, viếtmain.pyimport và chạy.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
ZeroDivisionError | Chia cho len của list rỗng | Kiểm tra rỗng trước |
Hàm trả None ngoài ý muốn | Nhánh if thiếu return | Rà mọi nhánh |
Báo cáo in ra None | print(bao_cao(...)) khi hàm đã in | Chọn một: trả hoặc in |
| Trung bình sai | Cộng nhầm cột hoặc chia sai len | Kiểm tra bằng tay một ca nhỏ |
| Mất người trùng tên | Gom vào dict theo tên | Giữ list các dòng |
KeyError khi đọc stats | Đổi tên khóa | Thống nhất tên khóa |
| Số lẻ thập phân dài | Không định dạng | :.2f khi hiển thị |
Tóm tắt
thong_ketrả dict hoặcNone; không in, không chia khi rỗng.bao_caochỉ định dạng, tách khỏi phép tính.- Trả chuỗi giúp kiểm thử và tái sử dụng ở CLI/API/file.
- Mean và median cùng in để phát hiện ngoại lai.
- Kiểm tra một ca nhỏ bằng tay trước khi tin kết quả.
Use ← → to move between lessons