Skip to content

Commit cf5e34a

Browse files
authored
Merge pull request #2 from Ninjaclasher/master
Add different checkers to the judge
2 parents d1c9886 + e5c933e commit cf5e34a

File tree

3 files changed

+187
-15
lines changed

3 files changed

+187
-15
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@ An offline equivalent of an online judge(dmoj.ca) in Python. Unfortunately, it u
1313

1414
The judge outputs one line for every case, indicating the test case name/id, verdict and the memory/cpu usage. A final line is then appended, indicating the number of cases passed, the final verdict, the maximum memory usage on any test case, and the total cpu usage.
1515

16-
Note: The memory usage is given in this format: "256p/1M", where 256 is the number of memory pages used.
16+
Note: The memory usage is given in this format: "256p/1M", where 256 is the number of memory pages used.
17+
18+
For speed performance reasons, this script comes with a C checker, which is up to 5 times faster than the Python equivalent.
19+
To compile the C checker, run:
20+
```
21+
gcc -shared -o _checker.so -fPIC _checker.c
22+
```
23+
in the same directory as `judge`.

_checker.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include <Python.h>
2+
3+
#define UNREFERENCED_PARAMETER(p)
4+
#if defined(_MSC_VER)
5+
# define inline __declspec(inline)
6+
# pragma warning(disable: 4127)
7+
# undef UNREFERENCED_PARAMETER
8+
# define UNREFERENCED_PARAMETER(p) (p)
9+
#elif !defined(__GNUC__)
10+
# define inline
11+
#endif
12+
13+
#if PY_MAJOR_VERSION >= 3
14+
#define PyStr_Check PyBytes_Check
15+
#define PyStr_Size PyBytes_Size
16+
#define PyStr_AsString PyBytes_AsString
17+
#else
18+
#define PyStr_Check PyString_Check
19+
#define PyStr_Size PyString_GET_SIZE
20+
#define PyStr_AsString PyString_AS_STRING
21+
#endif
22+
23+
static inline int isline(char ch) {
24+
switch (ch) {
25+
case '\n':
26+
case '\r':
27+
return 1;
28+
}
29+
return 0;
30+
}
31+
32+
static inline int iswhite(char ch) {
33+
switch (ch) {
34+
case ' ':
35+
case '\t':
36+
case '\v':
37+
case '\f':
38+
case '\n':
39+
case '\r':
40+
return 1;
41+
}
42+
return 0;
43+
}
44+
45+
static int check_standard(const char *judge, size_t jlen, const char *process, size_t plen) {
46+
size_t j = 0, p = 0;
47+
int nj, np;
48+
49+
while (j < jlen && iswhite(judge[j])) ++j;
50+
while (p < plen && iswhite(process[p])) ++p;
51+
for (;;) {
52+
nj = np = 0;
53+
while (j < jlen && ((nj |= isline(judge[j])), iswhite(judge[j]))) ++j;
54+
while (p < plen && ((np |= isline(process[p])), iswhite(process[p]))) ++p;
55+
if (j == jlen || p == plen) return j == jlen && p == plen;
56+
if (nj != np) return 0;
57+
58+
while (j < jlen && !iswhite(judge[j])) {
59+
if (p >= plen) return 0;
60+
if (judge[j++] != process[p++]) return 0;
61+
}
62+
}
63+
}
64+
65+
static PyObject *checker_standard(PyObject *self, PyObject *args) {
66+
PyObject *expected, *actual, *result;
67+
68+
UNREFERENCED_PARAMETER(self);
69+
if (!PyArg_ParseTuple(args, "OO:standard", &expected, &actual))
70+
return NULL;
71+
72+
if (!PyStr_Check(expected) || !PyStr_Check(actual)) {
73+
PyErr_SetString(PyExc_ValueError, "expected strings");
74+
return NULL;
75+
}
76+
77+
Py_INCREF(expected);
78+
Py_INCREF(actual);
79+
Py_BEGIN_ALLOW_THREADS
80+
result = check_standard(PyStr_AsString(expected), PyStr_Size(expected),
81+
PyStr_AsString(actual), PyStr_Size(actual)) ?
82+
Py_True : Py_False;
83+
Py_END_ALLOW_THREADS
84+
Py_DECREF(expected);
85+
Py_DECREF(actual);
86+
Py_INCREF(result);
87+
return result;
88+
}
89+
90+
static PyMethodDef checker_methods[] = {
91+
{"standard", checker_standard, METH_VARARGS,
92+
"Standard DMOJ checker."},
93+
{NULL, NULL, 0, NULL}
94+
};
95+
96+
#if PY_MAJOR_VERSION >= 3
97+
static struct PyModuleDef moduledef = {
98+
PyModuleDef_HEAD_INIT,
99+
"_checker",
100+
NULL,
101+
-1,
102+
checker_methods,
103+
NULL,
104+
NULL,
105+
NULL,
106+
NULL
107+
};
108+
109+
PyMODINIT_FUNC PyInit__checker(void) { (void) PyModule_Create(&moduledef); }
110+
#else
111+
PyMODINIT_FUNC init_checker(void) { (void) Py_InitModule("_checker", checker_methods); }
112+
#endif

judge

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
#!/usr/bin/env python3
22

33
"""
4-
This program is free software: you can redistribute it and/or modify
5-
it under the terms of the GNU General Public License as published by
6-
the Free Software Foundation, either version 3 of the License, or
7-
(at your option) any later version.
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
88
9-
This program is distributed in the hope that it will be useful,
10-
but WITHOUT ANY WARRANTY; without even the implied warranty of
11-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12-
GNU General Public License for more details.
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
1313
14-
You should have received a copy of the GNU General Public License
15-
along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
"""
1717

1818
import argparse
@@ -40,7 +40,54 @@ class Verdict(Enum):
4040
RE = 1 << 4
4141
WA = 1 << 5
4242

43-
def judge_case(file_i,file_o,cpu_limit,mem_limit,executable,out_limit=None):
43+
def iswhite(ch):
44+
return chr(ch) in [' ', '\t', '\v', '\f', '\n', '\r']
45+
46+
def isline(ch):
47+
return chr(ch) in ['\n', '\r']
48+
49+
def identical(judge, process):
50+
return judge == process
51+
52+
def standard(judge, process):
53+
try:
54+
#Use a C subprogram instead python for speed increase
55+
from _checker import standard as c_standard
56+
return c_standard(judge, process)
57+
except ImportError:
58+
j = 0
59+
p = 0
60+
jlen = len(judge)
61+
plen = len(process)
62+
while j < jlen and iswhite(judge[j]):
63+
j += 1
64+
while p < plen and iswhite(process[p]):
65+
p += 1
66+
while True:
67+
nj = False
68+
np = False
69+
while j < jlen and iswhite(judge[j]):
70+
nj |= isline(judge[j])
71+
j += 1
72+
while p < plen and iswhite(process[p]):
73+
np |= isline(process[p])
74+
p += 1
75+
if j == jlen or p == plen:
76+
return j == jlen and p == plen
77+
if nj != np:
78+
return False
79+
while j < jlen and not iswhite(judge[j]):
80+
if p >= plen:
81+
return False
82+
if judge[j] != process[p]:
83+
return False
84+
j += 1
85+
p += 1
86+
87+
def check(judge_output, process_output, checker=standard):
88+
return checker(judge_output, process_output)
89+
90+
def judge_case(file_i,file_o,cpu_limit,mem_limit,executable,checker, out_limit=None):
4491
r,w = os.pipe()
4592
os.set_inheritable(w,True)
4693
os.set_inheritable(2,True)
@@ -77,19 +124,24 @@ def judge_case(file_i,file_o,cpu_limit,mem_limit,executable,out_limit=None):
77124
if os.WEXITSTATUS(return_status) != 0:
78125
return Verdict.RE,rusage.ru_utime,rusage.ru_maxrss,return_status
79126
answer = open(file_o,"rb").read()
80-
if answer != result:
127+
if not check(answer, result, checker):
81128
return Verdict.WA,rusage.ru_utime,rusage.ru_maxrss,return_status
82129
return Verdict.AC,rusage.ru_utime,rusage.ru_maxrss,return_status
83130

84131
def main():
132+
checkers = {
133+
'standard' : standard,
134+
'identical': identical,
135+
}
85136
parser = argparse.ArgumentParser(description="An offline judging tool.")
86137
parser.add_argument("test_cases", help="Directory that contains the test cases to judge with.")
87138
parser.add_argument("cpu_limit", type=float, help="Time limit.")
88139
parser.add_argument("mem_limit", help="Memory limit.")
89140
parser.add_argument("executable", help="File to run.")
141+
parser.add_argument("checker", default="standard", nargs="?", choices=checkers.keys(), help="Checker to be used to compare outputs.")
90142
args = parser.parse_args()
91143
MEMORY_UNIT = {
92-
"B": 2**0,
144+
"B" : 2**0,
93145
"K" : 2**10,
94146
"M" : 2**20,
95147
"G" : 2**30,
@@ -104,6 +156,7 @@ def main():
104156
cpu_limit = args.cpu_limit
105157
mem_limit = (int(args.mem_limit[:-1]) * MEMORY_UNIT[args.mem_limit[-1:]]) & -mmap.PAGESIZE
106158
executable = args.executable
159+
checker = checkers[args.checker]
107160
num_ac = 0
108161
status_mask = 0
109162
cpu_total = 0.0
@@ -118,7 +171,7 @@ def main():
118171
recursive_judge(os.path.join(case,filename),depth+1)
119172
elif case.endswith(".in") and os.path.isfile(case) and os.path.isfile(case[:-2] + "out"):
120173
file_out = case[:-2] + "out"
121-
verdict,cpu_usage,mem_usage,return_status = judge_case(case,file_out,cpu_limit,mem_limit,executable,out_limit=os.path.getsize(file_out) * 4)
174+
verdict,cpu_usage,mem_usage,return_status = judge_case(case,file_out,cpu_limit,mem_limit,executable,checker,out_limit=os.path.getsize(file_out) * 4)
122175
cpu_total += cpu_usage
123176
mem_total = max(mem_usage,mem_total)
124177
status_mask |= verdict.value

0 commit comments

Comments
 (0)