Tạo bộ sinh bài toán cơ bản sử dụng phân tích cú pháp đệ quy

Tóm tắt dự án

Dự án này nhằm xây dựng một chương trình dòng lệnh có khả năng tự động tạo các bài toán cơ bản cho học sinh tiểu học. Chương trình đảm bảo tính duy nhất của mỗi bài toán và tối ưu hóa hiệu suất thông qua việc áp dụng kỹ thuật chuẩn hóa biểu thức.


1. Phân tích và thiết kế

1.1 Mục tiêu

  • Xây dựng chương trình tạo ra các bài toán số học bao gồm phép cộng, trừ, nhân, chia.
  • Đảm bảo không có bài toán trùng lặp.
  • Sử dụng cấu trúc dữ liệu phù hợp để tăng tốc độ xử lý.

1.2 Hiệu suất

Để cải thiện hiệu suất của chương trình:

  • Loại bỏ tính toán dư thừa: Bằng cách chuẩn hóa các biểu thức (canonical form), chúng ta tránh được việc tạo ra các bài toán tương tự nhau.
  • Tối ưu hóa sinh biểu thức: Chỉ thêm dấu ngoặc khi thật sự cần thiết để giảm thiểu độ phức tạp của biểu thức.
  • Sử dụng tập hợp (set): Lưu trữ các biểu thức đã chuẩn hóa trong một tập hợp để nhanh chóng kiểm tra trùng lặp.
  • Hạn chế độ sâu đệ quy: Đặt giới hạn số lượng toán tử và mức độ lồng ghép của dấu ngoặc để ngăn chặn đệ quy quá sâu.

2. Thực hiện

2.1 Cấu trúc mã nguồn

Chương trình được tổ chức thành các mô-đun sau:

  • Mô-đun sinh biểu thức:

  • tạo_số_hoàn_chỉnh: Sinh số tự nhiên hoặc phân số ngẫu nhiên.

  • sinh_biểu_thức: Sinh biểu thức số học theo yêu cầu về số lượng toán tử.

  • xóa_dấu_ngoặc_bên_ngoài: Loại bỏ các dấu ngoặc không cần thiết ở bên ngoài.

  • Mô-đun chuẩn hóa biểu thức:

  • chia_tách_biểu_thức: Chuyển đổi biểu thức từ dạng chuỗi sang danh sách token.

  • chuẩn_hóa_biểu_thức: Chuyển biểu thức về dạng chuẩn bằng cách sắp xếp các toán hạng theo thứ tự tăng dần đối với các phép toán giao hoán (+, *).

  • Công cụ hỗ trợ:

  • chuyển_số_thành_chuỗi: Chuyển đổi số phân số thành chuỗi hiển thị.

  • chuyển_chuỗi_thành_số: Ngược lại, chuyển chuỗi thành số phân số.

2.2 Quy trình hoạt động

  1. Chủ đạo:
  • Hàm chạy_chương_trình sẽ nhận đầu vào từ dòng lệnh và quyết định thực hiện chức năng nào (tạo bài toán hoặc chấm điểm).
  1. Tạo bài toán:
  • Gọi hàm sinh_các_bài_toán để tạo ra số lượng bài toán mong muốn.
  • Mỗi bài toán được kiểm tra tính duy nhất bằng cách so sánh với các biểu thức đã chuẩn hóa trước đó.
  1. Chấm điểm:
  • So sánh đáp án người dùng với kết quả đúng của bài toán.

3. Mã nguồn chi tiết

3.1 Hàm chuẩn hóa biểu thức

def chuẩn_hóa_biểu_thức(biểu_thức):
    """
    Chuyển biểu thức về dạng chuẩn để kiểm tra trùng lặp.
    Các phép toán giao hoán (+, *) sẽ được sắp xếp theo thứ tự tăng dần.
    """
    tokens = chia_tách_biểu_thức(biểu_thức)
    vị_trí = 0

    def phân_tích():
        phiêu = phân_tích_hạng_mục()
        while vị_trí < len(tokens) and tokens[vị_trí] in ('+', '-'):
            toán_tử = tokens[vị_trí]
            vị_trí += 1
            phải = phân_tích_hạng_mục()
            if toán_tử == '+':
                # Sắp xếp toán hạng nếu là phép cộng
                if phiêu > phải:
                    phiêu, phải = phải, phiêu
            phiêu = f"{phiêu}{toán_tử}{phải}"
        return phiêu

    def phân_tích_hạng_mục():
        phần_tử = phân_tích_yếu tố()
        while vị_trí < len(tokens) and tokens[vị_trí] in ('*', '/'):
            toán_tử = tokens[vị_trí]
            vị_trí += 1
            phải = phân_tích_yếu tố()
            if toán_tử == '*':
                # Sắp xếp toán hạng nếu là phép nhân
                if phần_tử > phải:
                    phần_tử, phải = phải, phần_tử
            phần_tử = f"{phần_tử}{toán_tử}{phải}"
        return phần_tử

    def phân_tích_yếu tố():
        phần_tử = tokens[vị_trí]
        if phần_tử == '(':
            vị_trí += 1
            nội_dung = phân_tích()
            if vị_trí >= len(tokens) or tokens[vị_trí] != ')':
                raise ValueError("Thiếu dấu ngoặc đóng")
            vị_trí += 1
            return f"({nội_dung})"
        else:
            vị_trí += 1
            return phần_tử

    chuẩn = phân_tích()
    return chuẩn

3.2 Hàm sinh biểu thức

def sinh_biểu_thức(số_toán_tử_tối_thiểu, số_toán_tử_tối_đa, giới_hạn_giá_trị):
    """
    Sinh biểu thức số học ngẫu nhiên với số lượng toán tử nằm trong khoảng [số_toán_tử_tối_thiểu, số_toán_tử_tối_đa].
    """
    if số_toán_tử_tối_đa == 0:
        giá_trị = tạo_số_hoàn_chỉnh(giới_hạn_giá_trị)
        return chuyển_số_thành_chuỗi(giá_trị)

    toán_tử = random.choice(['+', '-', '*', '/'])
    số_toán_tử_trái = random.randint(max(0, số_toán_tử_tối_thiểu - 1), số_toán_tử_tối_đa - 1)
    số_toán_tử_phải = số_toán_tử_tối_đa - 1 - số_toán_tử_trái

    phần_trái = sinh_biểu_thức(số_toán_tử_trái, số_toán_tử_trái, giới_hạn_giá_trị)
    phần_phải = sinh_biểu_thức(số_toán_tử_phải, số_toán_tử_phải, giới_hạn_giá_trị)

    # Thêm dấu ngoặc nếu cần thiết
    if toán_tử in ['+', '-'] and re.search(r'[*/]', phần_trái + phần_phải):
        biểu_thức = f"({phần_trái} {toán_tử} {phần_phải})"
    else:
        biểu_thức = f"{phần_trái} {toán_tử} {phần_phải}"

    return biểu_thức

3.3 Hàm sinh bài toán

def sinh_các_bài_toán(số_lượng, giới_hạn_giá_trị):
    """
    Sinh ra số_lượng bài toán không trùng lặp với giá trị nằm trong phạm vi [0, giới_hạn_giá_trị).
    """
    bài_toán = []
    chuẩn = set()

    while len(bài_toán) < số_lượng:
        thử = sinh_biểu_thức(1, 3, giới_hạn_giá_trị)  # Ít nhất 1 toán tử, nhiều nhất 3 toán tử
        chuẩn_hóa = chuẩn_hóa_biểu_thức(thử)
        if chuẩn_hóa not in chuẩn:
            chuẩn.add(chuẩn_hóa)
            bài_toán.append(thử)

    return bài_toán

4. Kiểm thử

4.1 Trường hợp kiểm thử

STT Biểu thức Kết quả mong đợi Ghi chú
1 7 * 1/8 + 1/4 * 4/5 9/10 Trộn phép cộng và phép nhân
2 (7/8 * 3/4) - 1/2 * 1/4 5/128 Trộn phép nhân và phép trừ
3 2 + 5/8 / 7 + 3/5 39/40 Có phép chia
4 5 - 1/2 - 2/3 * 0 9/2 Có phép trừ
5 1 * 4/5 * 5 * 2/5 8/5 Liên tiếp phép nhân

4.2 Kết quả kiểm thử

def kiểm_thử():
    các_thử_nghiệm = [
        {"biểu_thức": "7 * 1/8 + 1/4 * 4/5", "kết_quả": "9/10"},
        {"biểu_thức": "(7/8 * 3/4) - 1/2 * 1/4", "kết_quả": "5/128"},
        {"biểu_thức": "2 + 5/8 / 7 + 3/5", "kết_quả": "39/40"},
        {"biểu_thức": "5 - 1/2 - 2/3 * 0", "kết_quả": "9/2"},
        {"biểu_thức": "1 * 4/5 * 5 * 2/5", "kết_quả": "8/5"}
    ]

    for thử in các_thử_nghiệm:
        biểu_thức = thử["biểu_thức"]
        kỳ_vọng = thử["kết_quả"]
        chuẩn_hóa = chuẩn_hóa_biểu_thức(biểu_thức)
        kết_quả = phân_tích_biểu_thức(chia_tách_biểu_thức(biểu_thức))
        print(f"Biểu thức: {biểu_thức} = {kết_quả} (Kỳ vọng: {kỳ_vọng})")

5. Kết luận

Chương trình đã đạt được các mục tiêu đề ra, bao gồm:

  • Sinh ra các bài toán duy nhất và chính xác.
  • Tối ưu hóa hiệu suất thông qua việc chuẩn hóa biểu thức.
  • Xử lý tốt các trường hợp đặc biệt như phân số và dấu ngoặc.

Thẻ: python arithmetic-generator recursive-descent-parser

Đăng vào ngày 7 tháng 6 lúc 21:38