Loading docs/intro/testlib/checker.md +44 −31 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ Checker 从命令行参数读取到输入文件名、选手输出文件名、标 ## 简单的例子 ???+note 题目 给定两个整数 $a,b(-1000 \le a,b \le 1000)$,输出它们的和。 给定两个整数 $a,b$($-1000 \le a,b \le 1000$),输出它们的和。 这题显然不需要 checker 对吧,但是如果一定要的话也可以写一个: Loading @@ -20,6 +20,7 @@ int main(int argc, char* argv[]) { int pans = ouf.readInt(-2000, 2000, "sum of numbers"); // 假定标准输出是正确的,不检查其范围 // 之后我们会看到这并不合理 int jans = ans.readInt(); if (pans == jans) Loading Loading @@ -106,7 +107,7 @@ int main(int argc, char* argv[]) { 这个 checker 主要有两个问题: 1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。此时正确的操作是返回 Fail 状态。 1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。同时,如果标准输出不合法,也会产生 WA。对于这两种情况,正确的操作都是返回 Fail 状态。 2. 读入标准输出和选手输出的代码是重复的。在这道题中写两遍读入问题不大,只需要一个 `for` 循环;但是如果有一道题输出很复杂,就会导致你的 checker 结构混乱。重复代码会大大降低可维护性,让你在 debug 或修改格式时变得困难。 读入标准输出和选手输出的方式实际上是完全相同的,这就是我们通常编写一个用流作为参数的读入函数的原因。 Loading @@ -124,7 +125,9 @@ int n, m, s, t; // 这个函数接受一个流,从其中读入 // 检查路径的合法性并返回路径长度 // 如果路径非法,对于选手输出流它将返回 _wa, // 当 stream 为 ans 时,所有 stream.quitf(_wa, ...) // 和失败的 readXxx() 均会返回 _fail 而非 _wa // 也就是说,如果输出非法,对于选手输出流它将返回 _wa, // 对于标准输出流它将返回 _fail int readAns(InStream& stream) { // 读入输出 Loading Loading @@ -183,12 +186,22 @@ int main(int argc, char* argv[]) { 注意到这种写法我们同时也检查了标准输出是否合法,这样写 checker 让程序更短,且易于理解和 debug。此种写法也适用于输出 YES(并输出方案什么的),或 NO 的题目。 ???+ note 对于某些限制的检查可以用 `InStream::ensure/ensuref()` 函数更简洁地实现。如上例第 21 至 23 行也可以等价地写成如下形式: ```cpp stream.ensuref(!used[v - 1], "vertex %d was used twice", v); ``` ???+ warning 请在 `readAns` 中避免调用**全局**函数 `::ensure/ensuref()`,这会导致在某些应判为 Wrong Answer 的选手输出下返回 `_fail`,产生错误。 ## 建议与常见错误 - 编写 readAns 函数,它真的可以让你的 checker 变得很棒。 - 读入选手输出时永远限定好范围,如果某些变量忘记了限定且被用于某些参数,你的 checker 可能会判定错误或 RE 等。 ### 反面教材 ##### 反面教材 ```cpp // .... Loading @@ -204,7 +217,7 @@ int x = // .... ``` ### 正面教材 ##### 正面教材 ```cpp // .... Loading docs/intro/testlib/general.md +31 −17 Original line number Diff line number Diff line Loading @@ -7,8 +7,8 @@ | Ok | `_ok` | 答案正确。 | | Wrong Answer | `_wa` | 答案错误。 | | Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | | Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $200$(可能的最大分数)。 | | Fail | `_fail` | 程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | | Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $100$(可能的最大分数)。 | | Fail | `_fail` | validator 中表示输入不合法,不通过校验。<br>checker 中表示程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | 通常用程序的返回值表明结果,但是也有一些其他方法:创建一个输出 xml 文件、输出信息到 stdout 或其他位置…… 这些都通过下方函数表中的 `quitf` 函数来完成。 Loading @@ -16,9 +16,9 @@ | 对象 | 含义 | | ----- | ----- | | `inf` | 标准输入流 | | `inf` | 输入文件流 | | `ouf` | 选手输出流 | | `ans` | 标准输出流 | | `ans` | 参考输出流 | ## 通用函数 Loading @@ -26,10 +26,10 @@ | 调用 | 含义 | | ----------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `void registerTestlibCmd()` | 注册 checker | | `void registerInteraction()` | 注册 interactor | | `void registerValidation()` | 注册 validator | | `void registerGen()` | 注册 generator | | `void registerTestlibCmd(int argc, char* argv[])` | 注册程序为 checker | | `void registerInteraction(int argc, char* argv[])` | 注册程序为 interactor | | `void registerValidation()` | 注册程序为 validator | | `void registerGen(int argc, char* argv[], int randomGeneratorVersion)` | 注册程序为 generator<br>`randomGeneratorVersion` 推荐为 `1` | | `void quit(TResult verdict, string message)`/`void quitf(TResult verdict, string message, ...)` | 结束程序,返回 `verdict`,输出 `message` | | `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | Loading Loading @@ -73,17 +73,31 @@ ## 使用项别名 推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, n)` 而非 `inf.readInt(1, 100)`,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]`。 推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, "n")` 而非 `inf.readInt(1, 100)`,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]`。 ## 使用 `ensure/ensuref` ## 使用 `ensure/ensuref()` 这两个函数用于检查条件是否成立(类似于 `assert`)。例如检查 $x_i \neq y_i$,我们可以使用 `ensuref(x_i != y_i, "Graph can't contain loops")`。还可以使用 C 风格占位符如 `ensuref(s.length() % 2 == 0, "String's'should have even length, but s.length()=%d", int(s.length()))`。方便地,我们可以使用 `ensure(x> y)`,如果条件不满足报错将为 `FAIL Condition failed: "x > y"`。 这两个函数用于检查条件是否成立(类似于 `assert()`)。例如检查 $x_i \neq y_i$,我们可以使用 ```cpp ensuref(x_i != y_i, "Graph can't contain loops"); ``` 还可以使用 C 风格占位符如 ```cpp ensuref(s.length() % 2 == 0, "String 's' should have even length, but s.length()=%d", int(s.length())); ``` 方便地,我们可以使用 `ensure(x > y)`,如果条件不满足报错将为 `FAIL Condition failed: "x > y"`。 ???+ warning 注意成员与非成员 `ensure/ensuref` 的区别 注意全局与成员 `ensure/ensuref()` 的区别 非成员函数仅用于题目内部检查,如标准输出是否合法(标准输出挂是真的有可能的)。如果检查失败将返回 `_fail`,而非 `_wa` 或 `pe`。 全局函数 `::ensure/ensuref()` 多用于 generator 和 validator 中,如果检查失败将统一返回 `_fail`。 成员函数仅用于判断选手输出是否合法。无论 `Stream` 为何(即 `inf` 和 `ans` 也如此),都将返回 `_wa`。 成员函数 `InStream::ensure/ensuref()` 一般用于判断选手和参考程序的输出是否合法。当 `Stream` 为 `ouf` 时,返回 `_wa`;为 `inf`(一般不使用)或 `ans` 时,返回 `_fail`。详见 [Checker 页面](./checker.md) 对于“`readAns` 模式”的说明。 **本文翻译并综合自[Testlib - Codeforces](https://codeforces.com/testlib)系列。`testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。** Loading
docs/intro/testlib/checker.md +44 −31 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ Checker 从命令行参数读取到输入文件名、选手输出文件名、标 ## 简单的例子 ???+note 题目 给定两个整数 $a,b(-1000 \le a,b \le 1000)$,输出它们的和。 给定两个整数 $a,b$($-1000 \le a,b \le 1000$),输出它们的和。 这题显然不需要 checker 对吧,但是如果一定要的话也可以写一个: Loading @@ -20,6 +20,7 @@ int main(int argc, char* argv[]) { int pans = ouf.readInt(-2000, 2000, "sum of numbers"); // 假定标准输出是正确的,不检查其范围 // 之后我们会看到这并不合理 int jans = ans.readInt(); if (pans == jans) Loading Loading @@ -106,7 +107,7 @@ int main(int argc, char* argv[]) { 这个 checker 主要有两个问题: 1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。此时正确的操作是返回 Fail 状态。 1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。同时,如果标准输出不合法,也会产生 WA。对于这两种情况,正确的操作都是返回 Fail 状态。 2. 读入标准输出和选手输出的代码是重复的。在这道题中写两遍读入问题不大,只需要一个 `for` 循环;但是如果有一道题输出很复杂,就会导致你的 checker 结构混乱。重复代码会大大降低可维护性,让你在 debug 或修改格式时变得困难。 读入标准输出和选手输出的方式实际上是完全相同的,这就是我们通常编写一个用流作为参数的读入函数的原因。 Loading @@ -124,7 +125,9 @@ int n, m, s, t; // 这个函数接受一个流,从其中读入 // 检查路径的合法性并返回路径长度 // 如果路径非法,对于选手输出流它将返回 _wa, // 当 stream 为 ans 时,所有 stream.quitf(_wa, ...) // 和失败的 readXxx() 均会返回 _fail 而非 _wa // 也就是说,如果输出非法,对于选手输出流它将返回 _wa, // 对于标准输出流它将返回 _fail int readAns(InStream& stream) { // 读入输出 Loading Loading @@ -183,12 +186,22 @@ int main(int argc, char* argv[]) { 注意到这种写法我们同时也检查了标准输出是否合法,这样写 checker 让程序更短,且易于理解和 debug。此种写法也适用于输出 YES(并输出方案什么的),或 NO 的题目。 ???+ note 对于某些限制的检查可以用 `InStream::ensure/ensuref()` 函数更简洁地实现。如上例第 21 至 23 行也可以等价地写成如下形式: ```cpp stream.ensuref(!used[v - 1], "vertex %d was used twice", v); ``` ???+ warning 请在 `readAns` 中避免调用**全局**函数 `::ensure/ensuref()`,这会导致在某些应判为 Wrong Answer 的选手输出下返回 `_fail`,产生错误。 ## 建议与常见错误 - 编写 readAns 函数,它真的可以让你的 checker 变得很棒。 - 读入选手输出时永远限定好范围,如果某些变量忘记了限定且被用于某些参数,你的 checker 可能会判定错误或 RE 等。 ### 反面教材 ##### 反面教材 ```cpp // .... Loading @@ -204,7 +217,7 @@ int x = // .... ``` ### 正面教材 ##### 正面教材 ```cpp // .... Loading
docs/intro/testlib/general.md +31 −17 Original line number Diff line number Diff line Loading @@ -7,8 +7,8 @@ | Ok | `_ok` | 答案正确。 | | Wrong Answer | `_wa` | 答案错误。 | | Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | | Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $200$(可能的最大分数)。 | | Fail | `_fail` | 程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | | Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $100$(可能的最大分数)。 | | Fail | `_fail` | validator 中表示输入不合法,不通过校验。<br>checker 中表示程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | 通常用程序的返回值表明结果,但是也有一些其他方法:创建一个输出 xml 文件、输出信息到 stdout 或其他位置…… 这些都通过下方函数表中的 `quitf` 函数来完成。 Loading @@ -16,9 +16,9 @@ | 对象 | 含义 | | ----- | ----- | | `inf` | 标准输入流 | | `inf` | 输入文件流 | | `ouf` | 选手输出流 | | `ans` | 标准输出流 | | `ans` | 参考输出流 | ## 通用函数 Loading @@ -26,10 +26,10 @@ | 调用 | 含义 | | ----------------------------------------------------------------------------------------------- | --------------------------------------------------- | | `void registerTestlibCmd()` | 注册 checker | | `void registerInteraction()` | 注册 interactor | | `void registerValidation()` | 注册 validator | | `void registerGen()` | 注册 generator | | `void registerTestlibCmd(int argc, char* argv[])` | 注册程序为 checker | | `void registerInteraction(int argc, char* argv[])` | 注册程序为 interactor | | `void registerValidation()` | 注册程序为 validator | | `void registerGen(int argc, char* argv[], int randomGeneratorVersion)` | 注册程序为 generator<br>`randomGeneratorVersion` 推荐为 `1` | | `void quit(TResult verdict, string message)`/`void quitf(TResult verdict, string message, ...)` | 结束程序,返回 `verdict`,输出 `message` | | `void quitif(bool condition, TResult verdict, string message, ...)` | 如果 `condition` 成立,调用 `quitf(verdict, message, ...)` | Loading Loading @@ -73,17 +73,31 @@ ## 使用项别名 推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, n)` 而非 `inf.readInt(1, 100)`,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]`。 推荐给 `readInt/readInteger/readLong/readDouble/readWord/readToken/readString/readLine` 等的有限制调用最后多传入一个 `string` 参数,即当前读入的项的别名,使报错易读。例如使用 `inf.readInt(1, 100, "n")` 而非 `inf.readInt(1, 100)`,报错信息将为 `FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100]`。 ## 使用 `ensure/ensuref` ## 使用 `ensure/ensuref()` 这两个函数用于检查条件是否成立(类似于 `assert`)。例如检查 $x_i \neq y_i$,我们可以使用 `ensuref(x_i != y_i, "Graph can't contain loops")`。还可以使用 C 风格占位符如 `ensuref(s.length() % 2 == 0, "String's'should have even length, but s.length()=%d", int(s.length()))`。方便地,我们可以使用 `ensure(x> y)`,如果条件不满足报错将为 `FAIL Condition failed: "x > y"`。 这两个函数用于检查条件是否成立(类似于 `assert()`)。例如检查 $x_i \neq y_i$,我们可以使用 ```cpp ensuref(x_i != y_i, "Graph can't contain loops"); ``` 还可以使用 C 风格占位符如 ```cpp ensuref(s.length() % 2 == 0, "String 's' should have even length, but s.length()=%d", int(s.length())); ``` 方便地,我们可以使用 `ensure(x > y)`,如果条件不满足报错将为 `FAIL Condition failed: "x > y"`。 ???+ warning 注意成员与非成员 `ensure/ensuref` 的区别 注意全局与成员 `ensure/ensuref()` 的区别 非成员函数仅用于题目内部检查,如标准输出是否合法(标准输出挂是真的有可能的)。如果检查失败将返回 `_fail`,而非 `_wa` 或 `pe`。 全局函数 `::ensure/ensuref()` 多用于 generator 和 validator 中,如果检查失败将统一返回 `_fail`。 成员函数仅用于判断选手输出是否合法。无论 `Stream` 为何(即 `inf` 和 `ans` 也如此),都将返回 `_wa`。 成员函数 `InStream::ensure/ensuref()` 一般用于判断选手和参考程序的输出是否合法。当 `Stream` 为 `ouf` 时,返回 `_wa`;为 `inf`(一般不使用)或 `ans` 时,返回 `_fail`。详见 [Checker 页面](./checker.md) 对于“`readAns` 模式”的说明。 **本文翻译并综合自[Testlib - Codeforces](https://codeforces.com/testlib)系列。`testlib.h` 的 GitHub 存储库为[MikeMirzayanov/testlib](https://github.com/MikeMirzayanov/testlib)。**