Commit 726d88e9 authored by Shiqing's avatar Shiqing
Browse files

Update testlib content

parent cef5bc0a
Loading
Loading
Loading
Loading
+44 −31
Original line number Diff line number Diff line
@@ -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,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
    // ....
@@ -204,7 +217,7 @@ int x =
                // ....
    ```

### 正面教材
    ##### 正面教材

    ```cpp
    // ....
+31 −17
Original line number Diff line number Diff line
@@ -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` 函数来完成。

@@ -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<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, ...)` |

@@ -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)。**