Trong bài này
Minh nộp bài "cộng hai số" lên VIMIC và nhận kết quả Sai kết quả, dù chạy thử trên máy vẫn thấy số 5 hiện ra. Nhìn kỹ thì chương trình in Ket qua: 5, còn đề chỉ cần đúng một số 5. Bài toán không khó, nhưng Minh chưa biết máy chấm đọc đầu ra theo cách nào. Bài này giúp bạn tránh những lỗi như vậy trước khi bước vào các thuật toán.
Máy chấm so đầu ra của bạn với đáp án từng ký tự
Máy chấm (online judge) là chương trình tự động chấm bài lập trình. Với mỗi bộ test, nó đưa một đoạn văn bản vào chương trình của bạn như thể ai đó gõ từ bàn phím. Văn bản này gọi là dữ liệu vào (viết tắt stdin). Mọi thứ chương trình print() ra là đầu ra (viết tắt stdout).
Đưa dữ liệu vàoVăn bản của bộ test thay cho bàn phímChạy chương trìnhTrong giới hạn thời gian và bộ nhớSo đầu raVới đáp án đúng của bộ testTrả kết quảĐạt, Sai kết quả, Quá thời gian…
Máy chấm không hiểu ý bạn. Nó chỉ so hai đoạn văn bản. Đề yêu cầu in tổng của hai số, chương trình dưới đây làm đúng điều đó:
2 3
a, b = map(int, input().split())
print(a + b)5
Đầu ra là 5, trùng với đáp án. Bây giờ thêm một dòng chữ cho "dễ đọc" như Minh đã làm:
2 3
a, b = map(int, input().split())
print("Ket qua:", a + b)Ket qua: 5
Con người đọc vẫn hiểu kết quả là 5. Máy chấm thì thấy đoạn Ket qua: 5 khác hẳn 5, nên trả Sai kết quả. Vì lý do đó, chương trình nộp bài không in lời mời nhập như Nhap a: và không in câu mô tả. Nó chỉ in đúng những gì phần "Dữ liệu ra" của đề mô tả.
Đọc dữ liệu theo đúng hình dạng của đề
Phần "Dữ liệu vào" của đề cho biết có bao nhiêu dòng và mỗi dòng chứa gì. Chương trình phải đọc đúng thứ tự đó. Ba hình dạng dưới đây chiếm phần lớn bài tập của khóa này.
Hình dạng thứ nhất: dòng đầu là n, dòng thứ hai có n số. Bạn đọc dòng thứ hai bằng split() để tách theo dấu cách, rồi đổi từng phần thành số:
4 7 9 10 6
n = int(input())
scores = list(map(int, input().split()))
print(len(scores), sum(scores))4 32
Chương trình in 4 32: danh sách có 4 phần tử và tổng điểm là 32. Hàm map(int, ...) gọi int cho từng phần sau khi tách, còn list(...) gom chúng thành danh sách.
Hình dạng thứ hai: dòng đầu là n, sau đó mỗi dòng một bản ghi. Mỗi bản ghi cần một lần đọc riêng, nên lời đọc nằm trong vòng lặp:
3 An 7 Binh 9 Chi 10
n = int(input())
for _ in range(n):
name, score = input().split()
print(name, int(score) + 1)An 8 Binh 10 Chi 11
Mỗi lần lặp đọc đúng một dòng và tách thành hai phần. Biến _ là tên quen dùng khi bạn không cần đến số thứ tự vòng lặp.
Hình dạng thứ ba: dòng đầu là T, số bộ test con trong cùng một lần chạy. Mỗi bộ test con lại có hình dạng riêng. Bạn bọc phần xử lý một bộ test con trong vòng lặp T lần:
2 3 5 1 4 2 8 8
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
print(max(a) - min(a))4 0
Bộ thứ nhất cho 5 - 1 = 4, bộ thứ hai cho 8 - 8 = 0. Mỗi bộ in đúng một dòng, theo đúng thứ tự xuất hiện.
Đọc sai hình dạng thường không cho kết quả sai mà làm chương trình dừng giữa chừng. Ví dụ, đề cho hai số trên một dòng nhưng chương trình đọc như thể dòng chỉ có một số:
2 3
a = int(input())
b = int(input())
print(a + b)Lỗi khi chạy, ở dòng 1
ValueError: invalid literal for int() with base 10: '2 3'
int() nhận cả chuỗi "2 3", có dấu cách ở giữa, nên không đổi được thành số. Máy chấm báo Lỗi khi chạy. Khi gặp lỗi này, việc đầu tiên là so cách đọc của bạn với phần "Dữ liệu vào" của đề.
In nhiều giá trị đúng định dạng
Đề thường yêu cầu in các số trên một dòng, cách nhau một dấu cách. In thẳng một list sẽ in cả dấu ngoặc vuông và dấu phẩy:
a = [3, 1, 2]
print(a)
print(*a)
print(*a, sep=",")[3, 1, 2] 3 1 2 3,1,2
Dòng đầu là cách Python hiển thị một danh sách, không phải định dạng đề cần. Dấu * trong print(*a) tách danh sách thành từng đối số, và print đặt một dấu cách giữa các đối số. Tham số sep đổi dấu cách đó thành ký tự khác.
Khi in rất nhiều dòng, gom chúng lại và in một lần nhanh hơn gọi print cho từng dòng. Hàm "\n".join(...) nối các chuỗi bằng ký tự xuống dòng:
results = [10, 20, 30]
print("\n".join(map(str, results)))10 20 30
join chỉ nối chuỗi, nên các số phải đổi thành chuỗi trước bằng map(str, ...). Kết quả giống hệt ba lần gọi print.
Đọc nhanh khi dữ liệu có hàng trăm nghìn số
Mỗi lần gọi input(), Python làm thêm vài việc phụ như kiểm tra lời mời nhập và bỏ ký tự xuống dòng. Với vài nghìn dòng, phần tốn thêm này không đáng kể. Với vài trăm nghìn dòng, nó bắt đầu chiếm một phần đáng kể của giới hạn thời gian.
Có hai cách đọc nhanh hơn. Cách thứ nhất thay input bằng sys.stdin.readline, đọc từng dòng như cũ:
An Binh
import sys
line = sys.stdin.readline()
print(repr(line))
print(repr(line.strip()))'An\n' 'An'
readline trả về cả ký tự xuống dòng \n. Khi đọc số, int() tự bỏ qua ký tự này. Khi đọc chuỗi như tên người, bạn cần strip() để bỏ nó đi, nếu không tên sẽ bị in kèm một dòng trống.
Cách thứ hai đọc toàn bộ dữ liệu một lần rồi tách thành các từ (phần nằm giữa các dấu cách và dấu xuống dòng). Cách này không quan tâm số nằm trên dòng nào, nên hợp với dữ liệu chỉ toàn số:
5 1 2 3 4 5
import sys
data = sys.stdin.read().split()
n = int(data[0])
a = list(map(int, data[1:1 + n]))
print(n, sum(a))5 15
Năm số nằm trên hai dòng khác nhau, nhưng read().split() vẫn trả về đủ các từ theo thứ tự. Chương trình in 5 15. Cách này cũng giúp đọc đến hết dữ liệu khi đề không cho trước số dòng.
Đọc kết quả chấm để biết sửa ở đâu
Mỗi bộ test cho một kết quả. Biết ý nghĩa của từng kết quả giúp bạn sửa đúng chỗ thay vì đoán.
| Kết quả trên VIMIC | Tên quốc tế | Ý nghĩa | Kiểm tra trước tiên |
|---|---|---|---|
| Đạt | AC (Accepted) | Đầu ra khớp đáp án | — |
| Sai kết quả | WA (Wrong Answer) | Chạy xong nhưng đầu ra khác đáp án | Định dạng in, trường hợp biên, công thức |
| Quá thời gian | TLE (Time Limit Exceeded) | Chạy quá giới hạn thời gian | Số bước của thuật toán, cách đọc dữ liệu |
| Lỗi khi chạy | RE (Runtime Error) | Chương trình dừng vì lỗi | Cách đọc dữ liệu, chỉ số ngoài danh sách, chia cho 0 |
| Vượt giới hạn CPU / bộ nhớ | MLE (Memory Limit Exceeded) | Dùng quá nhiều bộ nhớ | Danh sách quá lớn hoặc đệ quy quá sâu |
Bộ test ví dụ trong đề chỉ là một trong nhiều bộ test. Đạt ví dụ chưa có nghĩa là đạt cả bài. Trước khi nộp, hãy tự thử thêm vài dữ liệu nhỏ, nhất là trường hợp biên: n = 1, các số bằng nhau, số âm, hoặc không có phần tử nào thỏa điều kiện.
Một khung chương trình gọn giúp tách ba việc đọc, xử lý và in. Khi bị Sai kết quả, bạn biết ngay cần xem phần nào:
3 4 -2 9
import sys
def solve():
data = sys.stdin.read().split()
n = int(data[0])
a = list(map(int, data[1:1 + n]))
best = max(a)
print(best)
solve()9
Lỗi thường gặp
| Lỗi | Vì sao sai | Cách sửa |
|---|---|---|
In thêm chữ như Ket qua: hoặc Nhap n: | Máy chấm so từng ký tự, chữ thừa làm đầu ra lệch | Chỉ in đúng phần "Dữ liệu ra" mô tả |
In cả danh sách bằng print(a) | Đầu ra có [, ] và dấu phẩy | Dùng print(*a) hoặc " ".join(map(str, a)) |
Đọc int(input()) cho dòng có nhiều số | int() không đổi được chuỗi có dấu cách, chương trình dừng | Tách bằng split() rồi đổi từng phần |
Đọc tên bằng sys.stdin.readline() mà không strip() | Tên mang theo \n, đầu ra có dòng trống thừa | Thêm .strip() khi đọc chuỗi |
Tóm tắt
- Máy chấm đưa dữ liệu vào qua
stdinvà so đầu ra từng ký tự, nên chỉ in đúng phần đề yêu cầu. - Đọc theo hình dạng đề cho: một dòng nhiều số thì
split(), nhiều dòng thì đọc trong vòng lặp, nhiều bộ test thì bọc trong vòng lặpTlần. - In danh sách bằng
print(*a)hoặcjoin, không in thẳnglist. - Khi dữ liệu từ khoảng 2·10⁵ số trở lên, đọc bằng
sys.stdin.readlinehoặcsys.stdin.read().split(). - Mỗi kết quả chấm chỉ về một nhóm nguyên nhân: Sai kết quả là định dạng hoặc logic, Quá thời gian là tốc độ, Lỗi khi chạy thường là cách đọc dữ liệu.
Kiểm tra nhanh
Đọc thêm
- Tài liệu Python về
sys.stdin, cách đọc dữ liệu chuẩn. - Tài liệu Python về hàm
print, gồm tham sốsepvàend.