Offline Judge 離線程式評測系統

前言

最近我同學有在架設我們學校的線上評測系統,而一開始我們的系統非常不穩定,因此,他想到可以寫離線的程式答題系統緩解我們上課的需求。而我看到這個系統,我覺得這是一個不錯的想法,並依此加以改良,新增 TLE 判定和超時時會強制停止,並且可以跨平台使用。

使用(解題者)

一題會是一個資料夾,裡面會有隱藏檔案和非隱藏檔案。

檔案名稱 大致內容 是否隱藏
AC ascii art 的 AC 字樣 True
WA ascii art 的 WA 字樣 True
TLE ascii art 的 TLE 字樣 True
main.h 主要程式 原則上 True
Run.cpp 執行程式 False
Solve.h 使用者的程式 False
Description 題目敘述 False
README.txt 說明使用方法(如下) False

README.txt 內文

以下說明檔案功能及用法

  1. 編譯並執行 Run.cpp 可以獲得評測結果
  2. 程式碼請寫在 Solve.h 裡面
  3. 題目敘述在 Description 裡面,一般應該是 .txt,.md 或 .pdf
  4. 標準輸入輸出用 cin 和 cout 就可以了

以下說明答題結果

  1. AC 作答正確
  2. WA 輸出結果錯誤,或不符合題目要求
  3. TLE 執行時間過長
  4. 程式跳掉 一般來說都是 RE(runtime error)

以下規定請勿違反

  1. 擅自更改除 Solve 以外的檔案

貼心小提醒

由於 Solve 函式會被重複使用,因此變數就算開在全域也要記得初始化

截圖

AC

使用(出題者)

資料夾中還含有一個隱藏資料夾 TestCase,裡面存放有測試資料。裡面的檔案如下

檔案名稱 大致內容
Gen.cpp 預設的測試資料生成程式
GraphGen.h 圖論測資生成程式 ( 使用介紹 )
log.txt 測試資料們的基本資料
OJ 生成可以上傳到 UOJ 系統的測試資料

Gen.cpp

介紹各個函式的功能

函式 功能
RandomNumber(long long a,long long b) 生成一個隨機數 ( 範圍[a,b] )
RandomNumber(long long n) 生成一個隨機數 ( 範圍[1,n] )
solve() 共用解答函式 ( 有需要的話 )
SubTesk1(int a) 生成第 a 筆測資

如果覺得 RandomNumber 太長,GraphGen.h 裡面有相同功能的函式 Rand

其他都不是非常重要,主要是可以同時支援 UOJ 格式的測資。以下是 a+b problem 的範例。

code

#include<bits/stdc++.h>
#include "GraphGen.h"
using namespace std;

unsigned seed=chrono::steady_clock::now().time_since_epoch().count();
mt19937_64 rng=mt19937_64(seed);

long long RandomNumber(long long a,long long b){
    uniform_int_distribution<long long> dis(a,b);
    return dis(rng);
}

long long RandomNumber(long long n){
    return RandomNumber(1,n);
}

void solve(){
    ;
}

void SubTesk1(int a){
    string fileName=to_string(a);

    ofstream ques(fileName+".in"),ans(fileName+".out");

    int aa=RandomNumber(10000),b=RandomNumber(10000);

    ques<<aa<<" "<<b<<"\n";

    ans<<aa+b<<"\n"; 

    cout<<a<<endl;
}

void UOJ(int a){
    string fileName="./OJ\\a"+to_string(a);

    ofstream ques(fileName+".in"),ans(fileName+".out");

    int aa=RandomNumber(10000),b=RandomNumber(10000);

    ques<<aa<<" "<<b<<"\n";

    ans<<aa+b<<"\n"; 

    cout<<a<<endl;
}

void ExTest(int a){
    string fileName="./OJ\\ex_a"+to_string(a);

    ofstream ques(fileName+".in"),ans(fileName+".out");

    int aa=RandomNumber(10000),b=RandomNumber(10000);

    ques<<aa<<" "<<b<<"\n";

    ans<<aa+b<<"\n";

    cout<<a<<endl;
}

#define REP(i,a,b) for(int i=(a);i<(b);++i)
int main(){
    ios::sync_with_stdio(0);cin.tie(0);

    clock_t startTime=clock();
//  測資生成函式使用都放在這裡面
    REP(i,1,5+1){
        SubTesk1(i);
    }

    REP(i,1,10+1){
        UOJ(i);
    }

    REP(i,1,10+1){
        ExTest(i);
    }
//  End Of Processes
    clock_t endTime=clock();

    cout<<double(endTime-startTime)/1000<<"\n";
}

log.txt

只有兩行,第一行是測試資料數目,第二行是執行時間限制,以毫秒為單位。

TestCases:     5
TimeLimit(ms): 50

主程式

函式 功能
RunCode(int timeLimit) 執行使用者程式(會計算時間,盡量減少多餘程式碼)
RunTestCase(int testCase,int timeLimit) 完成執行第 testCase 筆測試資料所需的前置條件
Judge(int testCase) 評判解答正確性
RunSolution() 執行所有測試並輸出結果

code

#ifndef MAIN_H
#define MAIN_H

#include<bits/stdc++.h>
#include<conio.h>
#include "Solve.h"
using namespace std;

bool isfinish=false;
vector<int> costTime;

// TLE 例外物件
class TimeLimitExceeded : public exception{
    public :
        TimeLimitExceeded() : exception() {}
};

void RunCode(int timeLimit){
    isfinish=false;
    clock_t start=clock();

    Solve();

    isfinish=true;

    clock_t end=clock();

    if(end-start>timeLimit){
        throw TimeLimitExceeded();
        return;
    }

    costTime.emplace_back(end-start);
}

bool RunTestCase(int testCase,int timeLimit){
    string fileNum=to_string(testCase);

    string inputFile="./TestCase\\";
    inputFile+=fileNum;
    inputFile+=".in";

    string userOutput="./TestCase\\sol";
    userOutput+=fileNum;
    userOutput+=".out";

    freopen(inputFile.c_str(),"r",stdin);
    freopen(userOutput.c_str(),"w",stdout);

    thread run{RunCode,timeLimit};

    chrono::milliseconds s(timeLimit+20);
    this_thread::sleep_for(s);

    if(!isfinish){
        run.detach();
        throw TimeLimitExceeded();
        return false;
    }else{
        run.join();
        return true;
    }
}

bool Judge(int testCase){
    ifstream userOutput("./TestCase\\sol"+to_string(testCase)+".out");
    ifstream question("./TestCase\\"+to_string(testCase)+".in");
    ifstream answer("./TestCase\\"+to_string(testCase)+".out");

    string tp,userAns,systemAns;

    while(userOutput>>tp){
        userAns+=tp;
        if(userAns.size()>10000000){
            return false;
        }
    }

    while(answer>>tp){
        systemAns+=tp;
    }

    cerr<<" ";
    return systemAns==userAns;
}

void RunSolution(){
    ifstream log("./TestCase\\log.txt");

    string recycle;
    int testCases,timeLimit;

    log>>recycle>>testCases;
    log>>recycle>>timeLimit;

    cerr<<"There're "<<testCases<<" testcases."<<"\n";
    cerr<<"Running TestCase..."<<"\n";

    try{
        for(int i=1;i<=testCases;++i){
            RunTestCase(i,timeLimit);
            cerr<<i<<" ";
        }
        cerr<<"\n\n";
    }catch(TimeLimitExceeded){
        ifstream TLE("TLE");
        string line;
        while(getline(TLE,line)){
            cerr<<line<<"\n";
        }
        cerr<<flush;
        getch();
        exit(0);
    }catch(exception){
        ifstream RE("RE");
        string line;
        while(getline(RE,line)){
            cerr<<line<<"\n";
        }
        cerr<<flush;
        getch();
        exit(0);
    }

    bool allCorrect=true;
    vector<bool> outputCorrect(testCases+1);

    for(int i=1;i<=testCases;++i){
        outputCorrect[i]=Judge(i);
        if(!outputCorrect[i]){
            allCorrect=false;
        }
    }

    cerr<<"\n";
    if(allCorrect){
        ifstream AC("AC");
        string line;
        while(getline(AC,line)){
            cerr<<line<<"\n";
        }
        cerr<<flush;
    }else{
        ifstream WA("WA");
        string line;
        while(getline(WA,line)){
            cerr<<line<<"\n";
        }
        cerr<<flush;
    }

    string ret[2]{"WA","AC"};
    cerr<<"For each testcase : "<<"\n\n";
    for(int i=1;i<=testCases;++i){
        cerr<<right<<setw(3)<<i<<". "<<flush;
        cerr<<ret[outputCorrect[i]]<<"  "<<flush;
        cerr<<"Execution time : "<<right<<setw(4)<<costTime[i-1]<<" ms"<<endl;
    }
    cerr<<flush;

    getch();
}

#endif

競賽中不會出現的 C++ 功能

多執行續 thread 函式庫

// 開新的執行續處理函式 RunCode
thread run{RunCode,timeLimit};
// 讓此執行續等待一段時間 chrono 格式
this_thread::sleep_for(chrono::milliseconds(timeLimit+20))
// 放空該執行續(與物件 run 取消綁定,但會繼續執行)
run.detach();
// 合併執行續,意味著它會等待到 run 那個執行序完成(return)後再合併
run.join();

其它題目

可以去雲端硬碟下載。這裡有一些題目。Mega