Trong bài này
Lan chuẩn bị thống kê số sách của 3 lớp. Một chương trình nhận các con số, tính tổng rồi gửi kết quả cho máy chấm. Lan cần đọc đúng dữ liệu và in đúng điều đề bài yêu cầu.
Máy chấm gửi đầu vào và nhận đầu ra
Một đề bài có thể cho số sách của hai lớp trên một dòng. Chương trình đọc hai số rồi chỉ in tổng:
12 15
a, b = map(int, input().split())
print(a + b)27
Ở đây input() nhận một dòng. split() tách dòng thành hai chuỗi, còn map(int, ...) chuyển chúng thành số nguyên. Máy chấm so sánh kết quả với đáp án của từng bộ dữ liệu. Dữ liệu trong ví dụ chỉ là một bộ thử; chương trình cần làm đúng cả các bộ khác thỏa điều kiện đề bài.
ĐọcNhận dữ liệu theo đề bàiXử lýTính kết quả từ dữ liệuInGửi kết quả theo định dạng yêu cầu
Không thêm lời nhắc như Nhap so: vào đầu ra nếu đề không yêu cầu. Một kết quả tính đúng nhưng kèm lời nhắc có thể khác đáp án. Khi tự thử, bạn có thể dùng print() để dò lỗi như Bài 14; trước khi nộp, hãy bỏ những dòng dò lỗi đó.
Đọc kỹ giới hạn và cấu trúc trước khi viết. Nếu đề nói dòng đầu là số lượng, đó là thông tin về số giá trị theo sau, không nhất thiết là một giá trị cần cộng. Bạn nên thử cả trường hợp nhỏ nhất và trường hợp các giá trị bằng nhau. Những ca đó giúp phát hiện lỗi chỉ số và lỗi xử lý rỗng.
readline() đọc lần lượt từng dòng
Python có thư viện chuẩn (standard library) đi kèm. Câu lệnh import cho phép dùng một mô-đun trong thư viện đó. Mô-đun sys cung cấp sys.stdin, luồng dữ liệu đầu vào của chương trình.
3 8
import sys
so_lop = int(sys.stdin.readline())
so_sach = int(sys.stdin.readline())
print(so_lop * so_sach)24
sys.stdin.readline() đọc một dòng mỗi lần gọi. Nếu dòng có ký tự xuống dòng ở cuối, chuỗi nhận được thường vẫn chứa ký tự đó. int() chấp nhận khoảng trắng bao quanh chữ số nên hai phép chuyển trên vẫn hoạt động.
Khi dò lỗi dữ liệu, repr cho thấy cách biểu diễn chuỗi, kể cả ký tự xuống dòng ẩn. Ví dụ sau hiện 'Lan\n' khi dòng nhập có ký tự xuống dòng:
Lan Binh
import sys
print(repr(sys.stdin.readline()))'Lan\n'
Với tên cần giữ lại, hãy chủ động bỏ ký tự xuống dòng khi thích hợp:
Lan
import sys
ten = sys.stdin.readline().rstrip("\n")
print("[" + ten + "]")[Lan]
rstrip("\n") bỏ các ký tự xuống dòng ở cuối, không xóa dấu cách trong tên. Ngược lại, strip() còn bỏ khoảng trắng ở hai đầu. Hãy chọn theo quy định của đề.
Đến cuối dữ liệu, readline() trả về chuỗi rỗng "". Đừng gọi int() trên chuỗi này. Nếu đề cho số dòng n, bạn có thể lặp đúng n lần thay vì đoán điểm kết thúc:
3 4 0 6
import sys
n = int(sys.stdin.readline())
dem = 0
for _ in range(n):
so_sach = int(sys.stdin.readline())
if so_sach > 0:
dem += 1
print(dem)2
Ở bộ thử này, vòng lặp đọc đúng ba dòng sau dòng đầu tiên và đếm hai dòng có số dương. Nếu n bằng 0, vòng lặp không chạy và số đếm vẫn là 0.
Một dòng trống trong dữ liệu khác với hết dữ liệu: dòng trống có thể được đọc thành "\n", còn hết dữ liệu cho "". Nếu đề cho phép dòng trống, không nên chuyển mọi dòng sang số mà chưa xét quy tắc đề bài. Nếu đề bảo đảm đủ số hợp lệ, có thể dựa vào bảo đảm đó và giữ chương trình đơn giản.
read().split() tách toàn bộ dữ liệu thành các phần
Khi đề chỉ chứa các giá trị cách nhau bằng khoảng trắng, bạn có thể đọc phần đầu vào còn lại trong một lần:
4 7 9
import sys
phan = sys.stdin.read().split()
print(phan)['4', '7', '9']
read() lấy toàn bộ phần chưa đọc. split() không có đối số tách theo các khoảng trắng, kể cả xuống dòng, và bỏ các khoảng trắng thừa giữa các phần. Kết quả là danh sách chuỗi: các chữ số chưa tự trở thành số nguyên.
Chuyển từng phần cần dùng trước khi tính:
4 7 9
import sys
phan = sys.stdin.read().split()
so = list(map(int, phan))
print(sum(so))20
Nếu dòng đầu là số lượng, nó cũng xuất hiện trong danh sách. Cần tách nó khỏi dữ liệu cần xử lý:
3 6 9 8
import sys
phan = sys.stdin.read().split()
n = int(phan[0])
diem = list(map(int, phan[1:1 + n]))
print(max(diem))9
Ví dụ này giả sử n ít nhất bằng 1 và đề đảm bảo đủ dữ liệu. Nếu n có thể bằng 0, cần quyết định riêng cách xử lý danh sách rỗng trước khi gọi max().
Một cách khác là tạo bộ lặp (iterator) bằng iter, rồi dùng next lấy lần lượt từng phần. So với lát cắt phan[1:1 + n], cách này giữ vị trí đọc giữa các lần gọi. Ví dụ giả sử đầu vào đủ n số:
3 6 9 8
import sys
phan = sys.stdin.read().split()
it = iter(phan)
n = int(next(it))
diem = []
for _ in range(n):
diem.append(int(next(it)))
print(diem)[6, 9, 8]
Nếu gọi next(it) khi đã hết phần tử, Python phát sinh StopIteration. Vì vậy, cần dựa vào bảo đảm dữ liệu của đề hoặc xử lý trường hợp thiếu dữ liệu.
| Chỉ số | Nội dung | Vai trò |
|---|---|---|
| 0 | 3 | Số lượng |
| 1 | 6 | Điểm thứ 1 |
| 2 | 9 | Điểm thứ 2 |
| 3 | 8 | Điểm thứ 3 |
read() giữ toàn bộ phần đã đọc trong bộ nhớ; readline() cho phép xử lý từng dòng. Hai cách đều đọc cùng luồng, nên một lần read() hết dữ liệu sẽ để lần đọc tiếp theo không còn dữ liệu. Chọn cách phù hợp với cấu trúc và kích thước đầu vào.
Tách theo khoảng trắng rất tiện khi số lượng số trên mỗi dòng không cố định. Tuy nhiên, cách này làm mất ranh giới giữa các dòng và số dấu cách ban đầu. Với bài cần giữ nguyên từng dòng văn bản, đọc theo dòng sẽ dễ đáp ứng yêu cầu hơn. Với dữ liệu lớn, hãy tính cả bộ nhớ để chứa chuỗi, danh sách phần và các số đã chuyển đổi.
12
import sys
tat_ca = sys.stdin.read()
so_tiep = int(sys.stdin.readline())Lỗi khi chạy, ở dòng 4
ValueError: invalid literal for int() with base 10: ''
Lần read() đã lấy hết dữ liệu. Lần readline() sau trả về "", gây ValueError khi chuyển bằng int().
math cung cấp các phép toán thường gặp
Dùng import math trước khi gọi tên hàm qua dấu chấm. Ví dụ, math.sqrt() tính căn bậc hai:
import math
dien_tich = 49
canh = math.sqrt(dien_tich)
print(canh)7.0
Kết quả là số thực 7.0. Với số âm, math.sqrt() không trả về căn số thực và gây ValueError:
import math
print(math.sqrt(-4))Lỗi khi chạy, ở dòng 3
ValueError: math domain error
Nếu đề cho phép số âm, hãy kiểm tra trước và xử lý theo yêu cầu của đề. Đừng tự thay đầu ra khi đề không quy định điều đó.
math.gcd() tìm ước chung lớn nhất (greatest common divisor) của các số nguyên:
import math
but = 18
vo = 24
nhom = math.gcd(but, vo)
print(nhom)
print(but // nhom, vo // nhom)6 3 4
Có thể chia thành 6 nhóm giống nhau, mỗi nhóm 3 bút và 4 vở. math.gcd(0, 0) bằng 0; không dùng kết quả đó làm số chia khi chưa kiểm tra.
Bạn cũng có thể nhập riêng các tên cần dùng bằng from math import. Khi đó gọi trực tiếp, không cần tiền tố math.:
from math import sqrt, gcd
print(sqrt(49))
print(gcd(18, 24))7.0 6
Dùng import math khi muốn thấy rõ nguồn của tên qua math. hoặc tránh trùng tên. Dùng from ... import ... khi chỉ cần vài tên và muốn viết ngắn hơn.
math.ceil() làm tròn lên tới số nguyên gần nhất không nhỏ hơn giá trị. math.floor() làm tròn xuống tới số nguyên gần nhất không lớn hơn giá trị:
import math
print(math.ceil(2.3), math.floor(2.3))
print(math.ceil(-2.3), math.floor(-2.3))3 2 -2 -3
Với số âm, floor(-2.3) là -3, không phải -2. int(-2.3) cắt phần thập phân về phía 0, nên không thay thế được floor() trong mọi trường hợp.
Giả sử một xe chở được 10 người và có 21 người cần đi. Tỉ số 21 / 10 là 2,1; số xe cần thuê là math.ceil(21 / 10), tức 3. Với dữ liệu chia hết như 20 người, kết quả vẫn là 2. Với 0 người, math.ceil(0 / 10) bằng 0, nên không cần thuê xe.
math.inf biểu diễn vô cực dương dưới dạng số thực. Nó hữu ích làm mốc ban đầu khi tìm giá trị nhỏ nhất:
import math
gia = [12, 7, 15]
nho_nhat = math.inf
for x in gia:
if x < nho_nhat:
nho_nhat = x
print(nho_nhat)7
Mọi giá hữu hạn trong ví dụ đều nhỏ hơn mốc ban đầu. Nếu danh sách rỗng, biến vẫn là inf; cần xử lý trường hợp đó theo đề thay vì coi nó là một mức giá.
float("inf") cũng tạo số thực vô cực dương bằng math.inf. Bạn có thể dùng cách này khi không cần nhập mô-đun math:
import math
moc = float("inf")
print(moc == math.inf)
print(15 < moc)True True
Đầu ra cần đúng số dòng và cách viết
Đề yêu cầu hai chữ số sau dấu thập phân thì dùng định dạng trong f-string:
a, b = 6, 8
trung_binh = (a + b) / 2
print(f"{trung_binh:.2f}")7.00
.2f tạo hai chữ số sau dấu chấm khi in, kể cả chữ số 0 ở cuối. Nó định dạng chuỗi hiển thị, không đổi giá trị đang lưu trong trung_binh.
Đề yêu cầu nhiều dòng thì in theo đúng thứ tự. Nếu yêu cầu các số trên một dòng, print(*ds) đặt một dấu cách giữa các phần tử:
diem = [8, 6, 9]
print(sum(diem))
print(*diem)23 8 6 9
Đề có thể yêu cầu dấu phân cách khác. Khi đó hãy dùng sep hoặc join phù hợp. Không tự thêm nhãn, dòng trống hay khoảng trắng cuối dòng. Quy tắc bỏ qua khoảng trắng phụ thuộc máy chấm, vì vậy hãy làm đúng mô tả thay vì dựa vào giả định.
Tách chương trình thành hàm đọc, xử lý và in
Một bài tính tổng điểm có ba bước rõ ràng. Các hàm sau giúp mỗi bước tập trung vào một việc:
3 7 8 9
import sys
def doc_du_lieu():
phan = sys.stdin.read().split()
n = int(phan[0])
return list(map(int, phan[1:1 + n]))
def tinh_tong(diem):
return sum(diem)
def in_ket_qua(tong):
print(tong)
diem = doc_du_lieu()
tong = tinh_tong(diem)
in_ket_qua(tong)24
doc_du_lieu() trả về danh sách, tinh_tong() nhận danh sách và trả về tổng. in_ket_qua() chỉ in kết quả. Có thể kiểm tra riêng hàm tính với một danh sách nhỏ, không cần nhập lại toàn bộ dữ liệu. Đề này giả sử đầu vào hợp lệ và đủ số điểm đã báo.
Nếu nhầm print() với return, hàm tính có thể in ra kết quả nhưng trả về None. Khi gọi hàm in tiếp, chương trình sẽ có thêm dòng không mong muốn. Hãy dùng return để chuyển giá trị giữa các bước.
Bạn có thể tự thử tinh_tong([]) và tinh_tong([4, 6]) trong một chương trình kiểm tra riêng. Hai ca này lần lượt kiểm tra danh sách rỗng và danh sách có nhiều số. Khi nộp bài, hãy bỏ các lệnh in kết quả kiểm tra để đầu ra chỉ chứa đáp án của dữ liệu được giao.
Ước lượng số lần lặp trước khi chạy
Xét đoạn code đếm số lần ghé một phần tử và số lần xét các cặp:
n = 4
mot_vong = 0
hai_vong = 0
for i in range(n):
mot_vong += 1
for i in range(n):
for j in range(n):
hai_vong += 1
print(mot_vong, hai_vong)4 16
Vòng đầu có 4 lượt. Hai vòng lồng nhau có 4 × 4 = 16 lượt ở thân trong cùng, kể cả các cặp có hai chỉ số bằng nhau. Với n = 100000, số lượt tương ứng khoảng 100000 và 10000000000. Mười tỷ lượt có thể mất rất lâu; thời gian thực còn phụ thuộc máy và công việc mỗi lượt.
Đây chỉ là cách ước lượng ban đầu, không phải đo thời gian chính xác. Hai vòng riêng biệt, mỗi vòng n lượt, cộng thành khoảng 2n, không phải n². Các cách phân tích sâu hơn sẽ được học ở khóa Nâng Cao.
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
Quên import sys | Tên sys chưa được định nghĩa. | Nhập mô-đun trước khi dùng. |
Cộng các phần từ split() | Các phần vẫn là chuỗi. | Chuyển sang int trước khi tính. |
Đọc tiếp sau read() | Phần đầu vào đã được đọc hết. | Chọn cách đọc và phân phối dữ liệu một lần. |
Gọi sqrt() với số âm | Không có căn bậc hai thực trong math.sqrt. | Kiểm tra miền dữ liệu theo đề. |
| In thêm lời nhắc | Đầu ra khác định dạng được yêu cầu. | Chỉ in kết quả theo đề. |
Lấy max() của danh sách rỗng | Danh sách không có giá trị lớn nhất. | Xử lý trường hợp rỗng theo đề. |
Tóm tắt
readline()đọc từng dòng;read().split()đọc phần còn lại rồi tách theo khoảng trắng.mathcósqrt,gcd,ceil,floorvàinfcho các nhu cầu khác nhau.- Tách đọc, tính và in để kiểm tra dễ hơn; đầu ra cần tuân thủ đề.
- Ước lượng số lượt lặp giúp nhận biết cách làm có thể quá chậm.