- 分享
testlib使用教程
- 2024-11-13 22:25:00 @
[TOC]
啥是testlib
Testlib.h
是一套历史悠久的库。它可以用于书写数据生成器(Generator),数据校验器(Validator),答案检查器(checker),交互器(interactor)。也是cn算法圈和俄罗斯算法圈官方使用的出题工具。
可以从这里下载(testlib.h),也可以从release界面下载头文件和所有示例。
也可以从本站下载
Generator
虽然用C/C++
自带的rand()
、mt19937
扔随机数也行,但是不能保证随机数可复现,也不能保证在不同环境下运行能得到同样的结果。所以推荐用testlib
来造数据。
一旦使用testlib
,就不能出现其他rand()
等任何标注库自带随机数。
完整的例子可以到github中查看。
简单的例子
生成两个范围内的随机数。
#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]就是命令行给定的第二个参数,也就是(./gen
是第个参数)。
当然上面的n
也可以用cin
或者直接赋值来给定。
testlib
支持正则表达式,生成字符串可以这样rnd.next("[a-z]{1,10}")
,会输出长度在的字符串,内容为小写字母。
成员函数
col | |
---|---|
rnd.next() |
生成 范围内的浮点数。 |
rnd.next(R) |
如果传入参数是整数,生成 范围内的整数。如果传入参数是浮点数,生成 范围内浮点数 |
rnd.next(L, R) |
如果传入参数是整数,生成 范围内的整数。如果传入参数是浮点数,生成 范围内浮点数 |
rnd.any(container) |
从指定的容器中(vector 、string 等返回一个) |
rnd.wnext(n, t) |
wnext() 是一个生成不等分布(具有偏移期望)的函数, 表示调用 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)}) 。如果 ,则为调用 次,取最小值;如果 ,等同于 next() 。 |
生成一棵树
这是codeforce
给出的生成树的代码, 表示顶点数, 表示伸展性。越大,越可能生成出一条链。越小,越可能生成菊花图(一般最大比大几倍,最小比小几倍即可)。
#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
生成了一组数据后,需要检验这个数据是否符合要求(是否满足数据范围,数据的性质是否正确什么的),这时候就需要写一个 来校验数据正确性。hack的时候也需要验证数据是否符合题目。
校验器的开头需要调用 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
。
三个输出流中都有下面这些成员函数:
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) | 和上一个一样,但是保证读入的范围为 |
vector<long long> readLongs(int n, long long L, long long R) |
读入一串long ,长度为n |
readInt 和readReal |
用法和readLong 的这一串都相同 |
string readString() 或者 string readLine() |
读入一行字符串。 |
void readEoln() |
读入换行符。在windows 和linux 下都能通用。win 环境读入\r\n ,linux 环境读入\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 |
---|---|---|
_ok |
输出正确,符合输出格式,代表最佳答案 | |
_wa |
输出错误或不是最优 | |
_pe |
输出不符合格式规范。判断起来会很麻烦,建议当作wa来处理。 | |
_fail |
检查器遇到了严重的内部错误,或std 的答案不正确,或参赛者找到了比std``更理想的答案。 |
有下面这些函数:
method | Description |
---|---|
stream.readXXX |
从inf 、ouf 、ans 中读出特定类型变量,其中XXX 和中的相同 |
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
的实现好一点,不然可能出现一题评测一分钟的情况。 checker
与validator
不同,不用特别检查空白字符。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