Hướng dẫn sử dụng Generators trong FPT-OJ


Khi có một lượng lớn dữ liệu kiểm tra, thay vì tạo từng tệp đầu vào và đầu ra, ta có thể sử dụng generator – một chương trình tạo dữ liệu tự động dựa trên tham số dòng lệnh.

1. Generator Node

Node generator có thể chứa:

  • Một chuỗi đơn: Tên của tệp chương trình tạo dữ liệu.
  • Một mảng: Phần tử đầu tiên là tệp nguồn (C hoặc C++), các phần tử còn lại là tệp phụ (ví dụ: tệp tiêu đề .h).
  • Một YAML associative array, chứa các khóa sau:
    • source: Chuỗi tên tệp generator hoặc một mảng gồm tệp nguồn và các tệp phụ.
    • language: Ngôn ngữ của generator (nếu không có, hệ thống tự suy đoán).
    • flags: Các cờ bổ sung khi biên dịch (mặc định là []).
    • compiler_time_limit: Giới hạn thời gian biên dịch (mặc định theo env.compiler_time_limit, khuyến nghị đặt 60 giây nếu dùng testlib.h).
    • time_limit: Giới hạn thời gian chạy generator (mặc định theo env.time_limit).
    • memory_limit: Giới hạn bộ nhớ của generator (mặc định theo env.memory_limit).

Có thể khai báo generator trong từng test case để sử dụng nhiều generator cho một bài toán.

2. Generator Arguments

Node generator_args chứa danh sách tham số truyền vào generator (ép kiểu thành str). Có thể khai báo ở mức tổng thể hoặc trong từng test case.

Ví dụ cấu hình YAML
generator: gen.cpp
test_cases:
  - {generator_args: [false, 123, "a b\nc"], points: 10}
  - {points: 20}
  • Test case 1: Generator nhận 4 tham số: "_aux_file", "False", "123", "a b\nc".
  • Test case 2: Không có generator_args, generator chỉ nhận "_aux_file".

3. Cách generator xuất dữ liệu

Generator cần:

  • Ghi dữ liệu đầu vào vào stdout.
  • Ghi dữ liệu đầu ra vào stderr.

Nếu một test case đã có tệp đầu vào (in) và đầu ra (out), generator sẽ không chạy cho test case đó.

4. Cách FPTOJ so sánh kết quả

FPTOJ sử dụng generator để sinh dữ liệu đầu vào và đầu ra đúng, sau đó so sánh với đầu ra của thí sinh:

  1. Thí sinh đọc dữ liệu từ stdin, sau đó thực hiện xử lý bài toán và in kết quả ra stdout.
  2. Generator sinh đầu vào ra stdout, và tính toán kết quả đúng ra stderr.
  3. FPTOJ chạy chương trình của thí sinh với đầu vào sinh bởi generator, rồi so sánh stdout của thí sinh với stderr của generator.

Nếu hai kết quả khớp nhau, bài làm được tính là đúng. Nếu khác nhau, thí sinh bị chấm sai.

Ví dụ:

  • Generator sinh đầu vào 10 20\n ra stdout.
  • Generator tính toán và in kết quả 30\n ra stderr.
  • Thí sinh chạy chương trình, đọc 10 20 từ stdin và in kết quả 30 ra stdout.
  • Hệ thống FPTOJ kiểm tra stdout (thí sinh) == stderr (generator), nếu đúng thì test case pass.

Cách này giúp giảm bớt việc lưu trữ tệp đầu vào và đầu ra cố định, đồng thời hỗ trợ test case linh hoạt hơn.

5. Ví dụ mã nguồn generator

Ví dụ 1 bài : https://fptoj.com/problem/chiadoan

Ví dụ 1: Sinh dãy số ngẫu nhiên
#include <iostream>
#include <cstdlib>
#include <ctime>

int main(int argc, char* argv[]) {
    std::srand(std::time(0));
    int n = (argc > 1) ? std::atoi(argv[1]) : 10; // Số phần tử, mặc định là 10

    std::cout << n << "\n"; // Ghi dữ liệu đầu vào vào stdout
    for (int i = 0; i < n; i++) {
        std::cout << (std::rand() % 100) << " ";
    }
    std::cout << "\n";

    std::cerr << "OK" << std::endl; // Ghi dữ liệu đầu ra vào stderr
    return 0;
}
Ví dụ 2: Sinh test case cho bài toán tìm ước chung lớn nhất (GCD)
#include <iostream>
#include <cstdlib>
#include <ctime>

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

int main(int argc, char* argv[]) {
    std::srand(std::time(0));
    int a = std::rand() % 1000 + 1;
    int b = std::rand() % 1000 + 1;

    std::cout << a << " " << b << "\n";
    std::cerr << gcd(a, b) << "\n";
    return 0;
}
Ví dụ 3: Sinh test case cho bài toán chuỗi đảo ngược
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>

std::string random_string(int length) {
    const std::string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    std::string result = "";
    for (int i = 0; i < length; i++) {
        result += chars[std::rand() % chars.length()];
    }
    return result;
}

int main(int argc, char* argv[]) {
    std::srand(std::time(0));
    int length = (argc > 1) ? std::atoi(argv[1]) : 10;
    std::string str = random_string(length);

    std::cout << str << "\n";
    std::cerr << std::string(str.rbegin(), str.rend()) << "\n";
    return 0;
}

Với hướng dẫn này, các thầy cô có thể dễ dàng áp dụng generator vào hệ thống FPT-OJ để quản lý dữ liệu test hiệu quả hơn!