Nghiên cứu lập lịch

Viết bộ kiểm chứng thời khóa biểu độc lập

Đọc lịch từ CSV, chỉ rõ lỗi theo từng dòng và đối chiếu điểm phạt mà không cần gọi lại bộ giải.

Thực hànhTrung cấp13 phút đọcPython

Thích và đánh giá Bình luận và hỏi đáp
Lịch CSV được kiểm tra cấu trúc trước, sau đó mới kiểm tra dữ liệu và tính điểm khi lịch khả thi.
Lịch CSV được kiểm tra cấu trúc trước, sau đó mới kiểm tra dữ liệu và tính điểm khi lịch khả thi. Mở hình lớn ↗

1. Xác định dữ liệu mà bộ kiểm chứng nhận

Một bộ giải có thể xuất lịch dưới dạng nguyên tử ASP, JSON hoặc CSV. Trước khi đánh giá chất lượng lịch, ta cần kiểm tra rằng tệp xuất mô tả đúng một phân công cho từng lớp và thỏa các quy tắc nghiệp vụ. Bài này dùng CSV với ba cột lop,ca,phong và dùng lại bốn lớp trong bài mô hình ASP.

lop,ca,phong
ctdl,t2,p101
pttk,t6,p101
kiemthu,t2,lab02
android,t4,lab02

Thông tin giảng viên, sức chứa, loại phòng và ca bận nằm trong dữ liệu chuẩn. Bộ kiểm chứng ghép mỗi dòng CSV với dữ liệu chuẩn trước khi chấm. Cách tổ chức này ngăn tệp lịch tự khai báo một phòng lớn hơn hoặc thay giảng viên của lớp để vượt qua kiểm tra.

2. Kiểm tra cấu trúc trước, kiểm tra ràng buộc sau

Tách lỗi thành hai nhóm. Nhóm thứ nhất là lỗi tệp, chẳng hạn sai tiêu đề hoặc thừa cột. Nhóm thứ hai là lỗi lịch, chẳng hạn xếp phòng chưa tồn tại, thiếu lớp, trùng giảng viên hoặc sai loại phòng. Ở nhóm thứ hai, trình kiểm chứng tích lũy nhiều lỗi trong một lần chạy để người điều chỉnh lịch có đủ thông tin.

Môi trường thực hành. Python 3.10 trở lên. Ví dụ dùng thư viện chuẩn Python.

Python · kiem_lich.pyTải mã nguồn
"""Chạy: python kiem_lich.py [lich.csv]. Tiêu đề CSV: lop,ca,phong."""
from collections import Counter
from csv import DictReader
from pathlib import Path
import sys

LOP = {
    "ctdl": ("a", 35, "lt"),
    "pttk": ("a", 30, "lt"),
    "kiemthu": ("b", 22, "th"),
    "android": ("c", 20, "th"),
}
PHONG = {"p101": (45, "lt"), "lab02": (24, "th")}
CA = {"t2", "t4", "t6"}
KHONG_RANH = {("c", "t2")}
MAU = [
    ("ctdl", "t2", "p101"), ("pttk", "t6", "p101"),
    ("kiemthu", "t2", "lab02"), ("android", "t4", "lab02"),
]


def doc_csv(duong_dan):
    with Path(duong_dan).open(encoding="utf-8-sig", newline="") as tep:
        reader = DictReader(tep)
        if reader.fieldnames != ["lop", "ca", "phong"]:
            raise ValueError("Tiêu đề phải là lop,ca,phong")
        rows = []
        for dong, row in enumerate(reader, start=2):
            if None in row or any(not isinstance(row[key], str) for key in reader.fieldnames):
                raise ValueError(f"Dòng {dong}: sai số cột")
            rows.append(tuple(row[key].strip() for key in reader.fieldnames))
        return rows


def kiem_lich(rows):
    loi = []
    dem_lop = Counter()
    phong_da_dung = {}
    gv_da_day = {}
    ca_cua_a = set()

    for vi_tri, (lop, ca, phong) in enumerate(rows, start=1):
        if lop not in LOP or ca not in CA or phong not in PHONG:
            loi.append(f"Bản ghi {vi_tri}: mã lớp, ca hoặc phòng không hợp lệ")
            continue

        dem_lop[lop] += 1
        gv, si_so, loai = LOP[lop]
        suc_chua, loai_phong = PHONG[phong]
        if si_so > suc_chua or loai != loai_phong:
            loi.append(f"Bản ghi {vi_tri}: phòng không phù hợp cho {lop}")
        if (gv, ca) in KHONG_RANH:
            loi.append(f"Bản ghi {vi_tri}: GV {gv} không rảnh vào {ca}")

        for da_xep, khoa, nhan in (
            (phong_da_dung, (ca, phong), f"trùng phòng {phong}"),
            (gv_da_day, (ca, gv), f"trùng giảng viên {gv}"),
        ):
            truoc = da_xep.setdefault(khoa, vi_tri)
            if truoc != vi_tri:
                loi.append(f"Bản ghi {vi_tri} và {truoc}: {nhan} vào {ca}")

        if gv == "a":
            ca_cua_a.add(ca)

    for lop in LOP:
        if dem_lop[lop] != 1:
            loi.append(f"Lớp {lop} có {dem_lop[lop]} phân công hợp lệ về mã; cần đúng 1")

    diem = None if loi else 5 * {"t2", "t6"}.issubset(ca_cua_a)
    return loi, diem


if __name__ == "__main__":
    try:
        rows = doc_csv(sys.argv[1]) if len(sys.argv) > 1 else MAU
    except (OSError, ValueError) as exc:
        sys.exit(str(exc))
    loi, diem = kiem_lich(rows)
    if loi:
        print("\n".join(loi))
        sys.exit(1)
    print(f"Lịch khả thi. Điểm phạt: {diem}")

Counter đếm số phân công theo lớp. Hai bảng băm lưu bản ghi đầu tiên sử dụng mỗi cặp (ca, phòng) hoặc (ca, giảng viên). Khi cặp đã xuất hiện, thông báo nêu cả bản ghi đang xét và bản ghi trước đó. Số bản ghi đếm theo dữ liệu đã đọc, tách khỏi số dòng vật lý dùng trong thông báo lỗi định dạng CSV.

Giải pháp kiểm tra trùng theo khóa giúp tránh duyệt mọi cặp lớp. Nếu phép tra bảng băm có chi phí trung bình hằng số, phần kiểm tra mất thời gian trung bình tỉ lệ với số bản ghi và số lớp trong dữ liệu chuẩn. Lịch có lỗi nhận None cho điểm phạt vì điểm được dùng để so sánh các lịch khả thi.

3. Kiểm chứng bằng các thay đổi nhỏ

Thay đổi so với lịch mẫuKết quả cần thấy
Giữ nguyênKhả thi, điểm 5
Chuyển pttk sang t4Khả thi, điểm 0
Chuyển pttk sang t2Trùng phòng và trùng giảng viên
Xếp Android vào p101Sai loại phòng
Bỏ dòng Kiểm thửThiếu phân công cho một lớp

Chạy python kiem_lich.py để kiểm tra lịch tích hợp hoặc python kiem_lich.py lich.csv để kiểm tra tệp. Mã thoát 1 giúp đưa bước kiểm tra vào quy trình thực nghiệm tự động. Nếu một bài chạy tạo ra hàng trăm lịch, hãy ghi mã dữ liệu, tệp lịch, trạng thái kiểm chứng và điểm do bộ kiểm chứng tính vào cùng báo cáo.

4. Mở rộng và kiểm tra tính độc lập

Khi có các ca có độ dài khác nhau, thay kiểm tra bằng nhau của mã ca bằng kiểm tra giao nhau của hai khoảng thời gian. Khi phòng nằm ở nhiều cơ sở, ghi thêm thời gian di chuyển và xác định khoảng nghỉ tối thiểu giữa hai lớp liên tiếp của cùng giảng viên. Tạo ca vừa đủ thời gian di chuyển và ca thiếu một phút để kiểm tra ranh giới.

Về mặt thực nghiệm, bộ kiểm chứng nên có bộ ca kiểm thử riêng, gồm nghiệm hợp lệ và lịch cố ý sai. Cùng một lịch CSV có thể do ASP, CP hoặc một người xếp thủ công tạo ra. Phán định của bộ kiểm chứng phải nhất quán nếu mọi phương án cùng tuân theo một đặc tả dữ liệu và quy tắc.