搭建Aqours Online Judge的琐琐碎碎(一)Presentation Error判定

事情开始于大二上学期,有一门叫做《网站建设与管理》的课程。本人由于对Web方面比较有兴趣,又比较喜欢Python语言的风格,于是就边学老师教的PHP边学了Django。正好大一下学期学了算法,对算法和各种OJ都比较有兴趣,所以就写了一个不能评测的手动OJ当期末大作业交了上去。这个暑假开始着手将这个网站建成一个能评测的真OJ。在知乎和Github上爬了一段时间,找到了https://github.com/KIDx/Judger这么一个开源项目,把这个项目的代码改动了不少之后终于可以跑起来了。自己的OJ地址在:http://111.230.131.247/JudgeHome/

目前使用之后发现原OJ系统对Presentation Error的判定过于严格,于是这里想将它改为去除行末空格与文末空行后比对的模式。将程序中与文本比对相关的算法提取出来,得到了一个用于文本比对的小程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 void compare_until_nonspace(int &c_std, int &c_usr, FILE *&fd_std, FILE *&fd_usr, int &ret)
 5 {
 6     while((isspace(c_std)) || (isspace(c_usr)))
 7     {
 8         if (c_std != c_usr)
 9         {
10             if (c_std == '\r' && c_usr == '\n')
11             {
12                 c_std = fgetc(fd_std);
13                 if (c_std != c_usr)
14                     ret = 3; //PRESENTATION ERROR
15             }
16             else
17             {
18                 ret = 3; //PRESENTATION ERROR
19             }
20         }
21         if (isspace(c_std))
22             c_std = fgetc(fd_std);
23         if (isspace(c_usr))
24             c_usr = fgetc(fd_usr);
25     }
26 }
27 
28 int tt_compare_output(string &file_std, string &file_usr)
29 {
30     int ret = 1; //ACCEPTED
31     int c_std, c_usr;
32     FILE *fd_std = fopen(file_std.c_str(), "r");
33     FILE *fd_usr = fopen(file_usr.c_str(), "r");
34 
35     if (fd_std == NULL)
36     {
37         printf("%s open standard file failed %s\n", strerror(errno), file_std.c_str());
38     }
39     
40     if (!fd_std || !fd_usr)
41     {
42         ret = -1; //RUNTIME ERROR (ABORTION)
43     }
44     else
45     {
46         c_std = fgetc(fd_std);
47         c_usr = fgetc(fd_usr);
48         for(;;)
49         {
50             compare_until_nonspace(c_std, c_usr, fd_std, fd_usr, ret);
51             while(!isspace(c_std) && !isspace(c_usr))
52             {
53                 //    LOG_DEBUG("std: %c usr: %c", c_std, c_usr);
54                 if (c_std == EOF && c_usr == EOF)
55                     goto end;
56                 if (c_std != c_usr)
57                 {
58                     ret = 2; //WRONG ANSWER
59                     goto end;
60                 }
61                 c_std = fgetc(fd_std);
62                 c_usr = fgetc(fd_usr);
63             }
64         }
65     }
66 end:
67     if (fd_std)
68         fclose(fd_std);
69     if (fd_usr)
70         fclose(fd_usr);
71     return ret;
72 }
73 
74 int main() {
75     string file_std = "std.txt";
76     string file_usr = "usr.txt";
77     int res = tt_compare_output(*&file_std, *&file_usr);
78     switch(res) {
79         case -1: cout << "RUNTIME ERROR (ABORTION)" << endl; break;
80         case 1: cout << "ACCEPTED" << endl; break;
81         case 2: cout << "WRONG ANSWER" << endl; break;
82         case 3: cout << "PRESENTATION ERROR" << endl; break;
83         default: cout << "SYSTEM ERROR" << endl;
84     }
85     return 0;
86 }

测试该程序,可知usr.txt与std.txt只要有任何一点微小的差别,程序就会输出PRESENTATION ERROR。增加了如下一个逻辑简单的函数之后,就可以实现先将标准输出和选手输出中的行末空格和文末空行都去掉再进行比较了。

void remove_blank(string file) {
    //删除文末空行和行末空格
    fstream target_file(file.c_str(), fstream::in | fstream::out);
    string line; //作为读取的一行
    string temp; //用作缓存
    vector<string> obj; //用于储存整理完成后的每一行
    if(!target_file) {
        cout << "Can't open target file!" << endl; //上线后改成LOG_DEBUG
    }
    while(getline(target_file, line)) {
        unsigned long len = line.length();
        int count = 0;
        for(unsigned long i=len-1; i>=0; i--) {
            int temp_char = line[i]; //用于将字符转换为ASCII码,保存ASCII码使用
            if(isspace(temp_char))
                count++;
            else break;
        }
        temp = line.substr(0, len-count);
        obj.push_back(temp);
    }
    int count_lines = 0; //文末空行的数量
    for(unsigned long i=obj.size()-1; i>=0; i--) {
        //去除文末空行
        if(obj[i].empty())
            count_lines++;
        else break;
    }
    cout << "count_lines: " << count_lines << endl;
    for(int i=0; i<count_lines; i++)
        obj.pop_back();
    //使用ofstream打开再关闭文件是为了清空源文件
    ofstream empty_file(file.c_str());
    empty_file.close();
    //重新打开文本文件
    fstream target(file.c_str(), fstream::out | fstream::in);
    if(!target) {
        cerr << "Can't open file" << endl; //上线后改成LOG_DEBUG
    }
    //写回源文件
    for(unsigned long i=0; i<obj.size(); i++)
        target << obj[i] << endl;
    target.close();
}

要注意的是,在Linux的g++编译器下,fstream、ofstream等的参数需要是c类型的字符串,需要将普通的string类型通过c_str转换为c类型的字符串才可以通过编译。而本人所使用的mac下clang编译器并没有这种要求,直接使用string类型也可以通过编译。这里debug了很久,一定要小心。

posted @ 2019-07-25 12:32  アカツキ  阅读(340)  评论(0编辑  收藏  举报