[TOC]

啥是testlib

Testlib.h 是一套历史悠久的库。它可以用于书写数据生成器(Generator),数据校验器(Validator),答案检查器(checker),交互器(interactor)。也是cn算法圈和俄罗斯算法圈官方使用的出题工具。

可以从这里下载(testlib.h),也可以从release界面下载头文件和所有示例。

也可以从本站下载

Generator

虽然用C/C++自带的rand()mt19937扔随机数也行,但是不能保证随机数可复现,也不能保证在不同环境下运行能得到同样的结果。所以推荐用testlib来造数据。

一旦使用testlib,就不能出现其他rand()等任何标注库自带随机数。

完整的例子可以到github中查看。

简单的例子

生成两个[1,n][1, n]范围内的随机数。

#include "testlib.h"
#include <iostream>

using namespace std;

int main(int argc, char* argv[]) {
    registerGen(argc, argv, 1);
    int n = atoi(argv[1]);
    cout << rnd.next(1, n) << " ";
    cout << rnd.next(1, n) << endl;
}

编译运行一下,可以发现无论运行多少次都会产生同样的数据。

registerGen()解析命令行参数,argv[1]就是命令行给定的第二个参数,也就是100 100 (./gen是第0 0 个参数)。 当然上面的n也可以用cin或者直接赋值来给定。

testlib支持正则表达式,生成字符串可以这样rnd.next("[a-z]{1,10}"),会输出长度在[1,10][1, 10]的字符串,内容为小写字母aza-z

成员函数

col
rnd.next() 生成 [0,1)[0, 1) 范围内的浮点数。
rnd.next(R) 如果传入参数是整数,生成 [0,R)[0, R) 范围内的整数。如果传入参数是浮点数,生成 [0,R][0, R] 范围内浮点数
rnd.next(L, R) 如果传入参数是整数,生成 [L,R)[L, R) 范围内的整数。如果传入参数是浮点数,生成 [L,R][L, R] 范围内浮点数
rnd.any(container) 从指定的容器中(vectorstring等返回一个)
rnd.wnext(n, t) wnext() 是一个生成不等分布(具有偏移期望)的函数, tt 表示调用 next() 的次数,并取生成值的最大值。例如 rnd.wnext(3, 1) 等同于 max({rnd.next(3), rnd.next(3)})rnd.wnext(4, 2) 等同于 max({rnd.next(4), rnd.next(4), rnd.next(4)}) 。如果 t<0t<0 ,则为调用 t-t 次,取最小值;如果 t=0t=0 ,等同于 next()

生成一棵树

这是codeforce给出的生成树的代码,nn 表示顶点数,tt 表示伸展性。tt越大,越可能生成出一条链。tt越小,越可能生成菊花图(一般最大比nn大几倍,最小比n-n小几倍即可)。

#include "testlib.h"
#include <iostream>

using namespace std;

void treeGenerator(int n, int t) {
    vector<int> p(n);

    // 给 1..n-1 号点设置父亲
    for (int i = 0; i < n; ++i)
        if (i > 0) p[i] = rnd.wnext(i, t);

    printf("%d\n", n);

    // 打乱节点排序
    vector<int> perm(n);
    for (int i = 0; i < n; ++i) perm[i] = i;
    shuffle(perm.begin() + 1, perm.end());

    // 随机加边
    vector<pair<int, int>> edges;
    for (int i = 1; i < n; i++)
        if (rnd.next(2))
            edges.push_back(make_pair(perm[i], perm[p[i]]));
        else
            edges.push_back(make_pair(perm[p[i]], perm[i]));

    // 打乱边的顺序
    shuffle(edges.begin(), edges.end());

    for (int i = 0; i + 1 < n; i++)
        printf("%d %d\n", edges[i].first + 1, edges[i].second + 1);
}

int main(int argc, char* argv[]) {
    registerGen(argc, argv, 1);

    int n = atoi(argv[1]);
    int t = atoi(argv[2]);

    treeGenerator(n, t);
}

生成多组数据

有两种方法,一种是给定好参数写个批处理就行。

./gen 10 20 > 1.in
./gen 50 -100 > 2.in
./gen 1000 1 > 3.in

然后运行这个脚本即可。

或者每次输出前调用startTest(int test_index)函数,会自动将数据输出到test_index文件中。但是还是需要用脚本加上.in后缀。

注意事项

  • 严格遵循题目的格式要求,如空格和换行。
  • 区域赛要求文件的末尾应有一个空白行(即最后一次输出后也需要换行)。
  • 对于大数据首选 printf 而非 cout ,以提高性能。(不建议在使用 Testlib 时关闭流同步)。
  • 不使用 UB(Undefined Behavior,未定义行为),输出如果写成 cout << rnd.next(1, n) << " " << rnd.next(1, n) << endl; ,则 rnd.next() 的调用顺序不同编译器结果不同。

Validator

生成了一组数据后,需要检验这个数据是否符合要求(是否满足数据范围,数据的性质是否正确什么的),这时候就需要写一个 ValidatorValidator 来校验数据正确性。hack的时候也需要验证hackhack数据是否符合题目。

校验器的开头需要调用 registerValidation(argc, argv) 完成初始化。

简单例子

检验A + B Problem的校验器

#include "testlib.h"
int main(int argc, char *argv[]) {
    registerValidation(argc, argv);
    inf.readInt(0, 10, "first integer");
    inf.readSpace();
    inf.readInt(0, 10, "second integer");
    
    inf.readEoln();
    inf.readEof();
    // 按照样例格式读一遍,检查是否少空格、回车,范围是否正确即可。
    // 如果格式错误会直接返回
    return 0;
}

编译出来运行如下: 可以看到不符合标准就直接提示错误。

要从输入文件(xx.in)读数据调用inf、要从选手输出读数据调用ouf,要从标准输出文件(xx.out)读数据调用ans

三个输出流中都有下面这些成员函数:

methodmethod DescriptionDescription
char readChar() 读取一个字符
char readChar(char c) 和上一个一样,但是保准读入的是字符c
char readSpace() 等同于readChar(' ')
string readToken() 读取一个词元(类似于不带空格的单词)
string readToken(string regex) 和上一个一样,但是保证读入内容符合正则表达式
long long readLong() 读入一个long long类型变量
long long readLong(long long L, long long R) 和上一个一样,但是保证读入的范围为[L,R][L, R]
vector<long long> readLongs(int n, long long L, long long R) 读入一串long,长度为n
readIntreadReal 用法和readLong的这一串都相同
string readString() 或者 string readLine() 读入一行字符串。
void readEoln() 读入换行符。在windowslinux下都能通用。win环境读入\r\nlinux环境读入\n
void readEof() 读入文件的末尾EOF

注意事项

  • validator对格式要求非常严格,任何一个空格、换行不符合都会提示错误。
  • Validator 最后必须读入文件结束符 EOF 表示校验结束。调用inf.readEof()

Checker

Checker,即 Special Judge,用于检验答案是否合法。使用 Testlib 可以让我们免去检验许多东西,使编写简单许多。在大部分OJ中,testlib也是唯一支持的库。

使用前需要在第一行加上registerTestlibCmd(argc, argv);初始化。

简单例子

下面是A + B Problem的检查器。

```cpp
#include "testlib.h"

int main(int argc, char** argv) {
    registerTestlibCmd(argc, argv);

    int a = inf.readLong();
    int b = inf.readLong();

    int out = ouf.readLong();
    if (a + b == out)
        quitf(_ok, "ok,pass the test"); // 这代表你的代码运行正确
    else
        quitf(_wa, "wrong answer: expected %d, but found %d\n", a + b, out); // 这代表你的代码运行错误
    return 0;
}

要从输入文件(xx.in)读数据调用inf、要从选手输出读数据调用ouf,要从标准输出文件(xx.out)读数据调用ans

checker的写法十分灵活,根据题目具体分体。有时候想checker的算法比想std还难。

下面是SDNU1720的checker。

#include "testlib.h"
#include <vector>
#include <iostream>

using namespace std;

int main(int argc, char* argv[]) {
    registerTestlibCmd(argc, argv);

    // 读取输入文件
    int N = inf.readInt();
    int M = inf.readInt();
    vector<int> u(M), v(M);
    vector<long long> w(M);
    for (int i = 0; i < M; ++i) {
        u[i] = inf.readInt();
        v[i] = inf.readInt();
        w[i] = inf.readLong();
    }

    // 读取输出文件
    vector<long long> x(N + 1);
    for (int i = 1; i <= N; ++i) {
        x[i] = ouf.readLong();
        if (x[i] < -1e18 || x[i] > 1e18) {
            quitf(_wa, "Value x[%d] = %lld is out of range [-1e18, 1e18]", i, x[i]);
        }
    }

    // 检查每条边是否满足条件
    for (int i = 0; i < M; ++i) {
        if (x[v[i]] - x[u[i]] != w[i]) {
            quitf(_wa, "Edge %d does not satisfy the condition: x[%d] - x[%d] != %lld", i + 1, v[i], u[i], w[i]);
        }
    }

    // 如果所有条件都满足
    quitf(_ok, "All conditions are satisfied.");
}

运行也很简单,运行命令为check <input_file> <output_file> <answer_file>

成员函数

quitf中有下面这几种返回类型:

Verdict test_micro meaning
OKOK _ok 输出正确,符合输出格式,代表最佳答案
Wrong AnswerWrong \space Answer _wa 输出错误或不是最优
Presentation ErrorPresentation \space Error _pe 输出不符合格式规范。判断起来会很麻烦,建议当作wa来处理。
FailFail _fail 检查器遇到了严重的内部错误,或std的答案不正确,或参赛者找到了比std``更理想的答案。

有下面这些函数:

method Description
stream.readXXX infoufans中读出特定类型变量,其中XXXvalidatorvalidator中的相同
void quit(TResult verdict, string message); 根据返回值给出评测结果并显示一条注释
void quitf(TResult verdict, const char* message, ...);
void quitif(bool condition, TResult verdict, const char* message, ...); 如果第一个参数为true, 则返回相应的result
void ensuref(bool condition, const char* message, ...); 等同于 assert。 检查条件是否为真,否则以 _fail 结束。 用于调试检查程序。

注意事项

  • 尽量checker的实现好一点,不然可能出现一题评测一分钟的情况。
  • checkervalidator 不同,不用特别检查空白字符。
  • quitf 的反馈信息应该尽量详细。这一方面方便选手了解问题出在何处,另一方面方便了调试。
  • 记得限定参数范围,防止 checker 出现访问非法内存等问题。
  • 更多细节参考Checkers with testlib.h

Interactor

交互器,用于交互题与选手程序交互。由于OJ的垃圾服务器,目测很难跑起来交互题评测,所以咕咕咕先。

评测方式配置

checker.cc等东西和测试数据一起上传。

在题目的config.yaml中加上这么两行即可:

checker_type: testlib
checker: checker.cc
validator: validator.cc # 可以不要val

或者在评测设置中比较器类型选择testlib,然后填上checker.cc

完整文档参考Briefly about testlib.h

0 comments

No comments so far...