From 726d88e978cc14cace441c064c1d3e9b1bdc795f Mon Sep 17 00:00:00 2001 From: Shiqing Date: Thu, 3 Jan 2019 08:56:00 +0800 Subject: [PATCH] Update testlib content --- docs/intro/testlib/checker.md | 75 +++++++++++++++++++++++++------------------ docs/intro/testlib/general.md | 48 +++++++++++++++++---------- 2 files changed, 75 insertions(+), 48 deletions(-) diff --git a/docs/intro/testlib/checker.md b/docs/intro/testlib/checker.md index 8fc09e6c..d7576822 100644 --- a/docs/intro/testlib/checker.md +++ b/docs/intro/testlib/checker.md @@ -7,7 +7,7 @@ Checker 从命令行参数读取到输入文件名、选手输出文件名、标 ## 简单的例子 ???+note 题目 - 给定两个整数 $a,b(-1000 \le a,b \le 1000)$,输出它们的和。 + 给定两个整数 $a,b$($-1000 \le a,b \le 1000$),输出它们的和。 这题显然不需要 checker 对吧,但是如果一定要的话也可以写一个: @@ -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) @@ -106,7 +107,7 @@ int main(int argc, char* argv[]) { 这个 checker 主要有两个问题: -1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。此时正确的操作是返回 Fail 状态。 +1. 它确信标准输出是正确的。如果选手输出比标准输出更优,它会被判成 WA,这不太妙。同时,如果标准输出不合法,也会产生 WA。对于这两种情况,正确的操作都是返回 Fail 状态。 2. 读入标准输出和选手输出的代码是重复的。在这道题中写两遍读入问题不大,只需要一个 `for` 循环;但是如果有一道题输出很复杂,就会导致你的 checker 结构混乱。重复代码会大大降低可维护性,让你在 debug 或修改格式时变得困难。 读入标准输出和选手输出的方式实际上是完全相同的,这就是我们通常编写一个用流作为参数的读入函数的原因。 @@ -124,7 +125,9 @@ int n, m, s, t; // 这个函数接受一个流,从其中读入 // 检查路径的合法性并返回路径长度 -// 如果路径非法,对于选手输出流它将返回 _wa, +// 当 stream 为 ans 时,所有 stream.quitf(_wa, ...) +// 和失败的 readXxx() 均会返回 _fail 而非 _wa +// 也就是说,如果输出非法,对于选手输出流它将返回 _wa, // 对于标准输出流它将返回 _fail int readAns(InStream& stream) { // 读入输出 @@ -183,39 +186,49 @@ int main(int argc, char* argv[]) { 注意到这种写法我们同时也检查了标准输出是否合法,这样写 checker 让程序更短,且易于理解和 debug。此种写法也适用于输出 YES(并输出方案什么的),或 NO 的题目。 -## 建议与常见错误 +???+ note + 对于某些限制的检查可以用 `InStream::ensure/ensuref()` 函数更简洁地实现。如上例第 21 至 23 行也可以等价地写成如下形式: -- 编写 readAns 函数,它真的可以让你的 checker 变得很棒。 -- 读入选手输出时永远限定好范围,如果某些变量忘记了限定且被用于某些参数,你的 checker 可能会判定错误或 RE 等。 + ```cpp + stream.ensuref(!used[v - 1], "vertex %d was used twice", v); + ``` -### 反面教材 +???+ warning + 请在 `readAns` 中避免调用**全局**函数 `::ensure/ensuref()`,这会导致在某些应判为 Wrong Answer 的选手输出下返回 `_fail`,产生错误。 -```cpp -// .... -int k = ouf.readInt(); -vector lst; -for (int i = 0; i < k; i++) // k = 0 和 k = -5 在这里作用相同(不会进入循环体) - lst.push_back( - ouf.readInt()); // 但是我们并不想接受一个长度为 -5 的 list,不是吗? -// .... -int pos = ouf.readInt(); -int x = - A[pos]; // 100% 会 RE。一定会有人输出 -42, 2147483456 或其他一些非法数字。 - // .... -``` +## 建议与常见错误 -### 正面教材 +- 编写 readAns 函数,它真的可以让你的 checker 变得很棒。 +- 读入选手输出时永远限定好范围,如果某些变量忘记了限定且被用于某些参数,你的 checker 可能会判定错误或 RE 等。 -```cpp -// .... -int k = ouf.readInt(0, n); // 负数会 PE -vector lst; -for (int i = 0; i < k; i++) lst.push_back(ouf.readInt()); -// .... -int pos = ouf.readInt(0, (int)A.size() - 1); // 防止 out of range -int x = A[pos]; -// .... -``` + ##### 反面教材 + + ```cpp + // .... + int k = ouf.readInt(); + vector lst; + for (int i = 0; i < k; i++) // k = 0 和 k = -5 在这里作用相同(不会进入循环体) + lst.push_back( + ouf.readInt()); // 但是我们并不想接受一个长度为 -5 的 list,不是吗? + // .... + int pos = ouf.readInt(); + int x = + A[pos]; // 100% 会 RE。一定会有人输出 -42, 2147483456 或其他一些非法数字。 + // .... + ``` + + ##### 正面教材 + + ```cpp + // .... + int k = ouf.readInt(0, n); // 负数会 PE + vector lst; + for (int i = 0; i < k; i++) lst.push_back(ouf.readInt()); + // .... + int pos = ouf.readInt(0, (int)A.size() - 1); // 防止 out of range + int x = A[pos]; + // .... + ``` - 使用项别名 diff --git a/docs/intro/testlib/general.md b/docs/intro/testlib/general.md index c8e8f5c4..e30d5be4 100644 --- a/docs/intro/testlib/general.md +++ b/docs/intro/testlib/general.md @@ -4,11 +4,11 @@ | 结果 | Testlib 别名 | 含义 | | ------------------ | ------------ | --------------------------------------------------------------- | -| Ok | `_ok` | 答案正确。 | -| Wrong Answer | `_wa` | 答案错误。 | -| Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | -| Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $200$(可能的最大分数)。 | -| Fail | `_fail` | 程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | +| Ok | `_ok` | 答案正确。 | +| Wrong Answer | `_wa` | 答案错误。 | +| Presentation Error | `_pe` | 答案格式错误。注意包括 Codeforces 在内的许多 OJ 并不区分 PE 和 WA。 | +| Partially Correct | `_pc(score)` | 答案部分正确。仅限于有部分分的测试点,其中 `score` 为一个正整数,从 $0$(没分)到 $100$(可能的最大分数)。 | +| Fail | `_fail` | validator 中表示输入不合法,不通过校验。
checker 中表示程序内部错误、标准输出有误或选手输出比标准输出更优,需要裁判 / 出题人关注。(也就是题目锅了) | 通常用程序的返回值表明结果,但是也有一些其他方法:创建一个输出 xml 文件、输出信息到 stdout 或其他位置…… 这些都通过下方函数表中的 `quitf` 函数来完成。 @@ -16,9 +16,9 @@ | 对象 | 含义 | | ----- | ----- | -| `inf` | 标准输入流 | +| `inf` | 输入文件流 | | `ouf` | 选手输出流 | -| `ans` | 标准输出流 | +| `ans` | 参考输出流 | ## 通用函数 @@ -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
`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, ...)` | @@ -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)。** -- 2.11.0