Testlib-Generator使用笔记
Testlib-Generator使用笔记
Testlib 使用来配合算法竞赛出题的工具,本文仅介绍其中的一个模块——数据生成器的使用方法。
Testlib 分为四部分:
- 编写 Generator,即数据生成器。
- 编写 Validator,即数据校验器,判断生成数据是否符合题目要求,如数据范围、格式等。
- 编写 Interactor,即交互器,用于交互题。
- 编写 Checker,即 Special Judge。
下载该项目,拷贝出 testlib.h 文件。
Testlib库仅有 testlib.h 这一个文件,使用时仅仅需要在编写的程序开头添加 #include "testlib.h"
即可。本文记录 Generator 数据生成器的常见使用方法。
从一个简单的例子开始
// clang-format off #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { // argc 与 argv 是命令行参数 registerGen(argc, argv, 1); int n = atoi(argv[1]); // 获取命令行的第一个参数 cout << rnd.next(1, n) << " "; cout << rnd.next(1, n) << endl; }
这个程序可以生成两个 范围内的整数,n可以通过程序执行时传入。
平时我们用的 rand()
或 C++11 的 mt19937/uniform_int_distribution
,当操作系统不同、使用不同编译器编译、不同时间运行等,它们的输出都可能不同(对于非常常用的 srand(time(0))
,这是显然的),而这就会给生成数据带来不确定性。而 Testlib 中的随机值生成函数则保证了相同调用会输出相同值,与 generator 本身或平台均无关。
需要注意的是,一旦使用了 Testlib,就不能再使用标准库中的 srand()
, rand()
等随机数函数,否则在编译时会报错。另外,不要使用 std::random_shuffle()
,请使用 Testlib 中的 shuffle()
,它同样接受一对迭代器。它使用 rnd
来打乱序列,即满足如上“好的 generator”的要求。
在一切之前,先执行 registerGen(argc, argv, 1)
初始化 Testlib(其中 1
是使用的 generator 版本,通常保持不变),然后我们就可以使用 rnd
对象来生成随机值。
随机数种子取自命令行参数的哈希值,对于某 generator g.cpp
, g 100
(类Unix系统) 和 g.exe "100"
(Windows 系统) 将会有相同的输出,而 g 100 0
则与它们不同。
rnd 的用法总结:
-
rnd.next(4)
:等概率生成一个[0,4)
范围内的整数 -
rnd.next(4, 100)
:等概率生成一个[4,100]
范围内的整数 -
rnd.next(4.0)
:等概率生成一个[0, 4.0)
范围内的浮点数 -
rnd.next("one | two | three")
:等概率返回one
、two
、three
中的一个 -
rnd.next("[1-9][0-9]{99}")
:长度为100的数字型字符串 -
rnd.wnext(4, 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()
。关于
rnd.wnext(i,t)
的形式化定义:另外,从官方给定的示例中,也支持传入两个范围参数:
-
rnd.any(container)
:等概率返回一个具有随机访问迭代器(如std::vector
和std::string
)的容器内的某一元素的引用
新特性:解析命令行参数
通常,我们使用 int a = atoi(argv[3])
来获取命令行中的参数,并将其转换为整数,但这么做有时候会出现一些问题:
- 不存在第三个参数时,这么做不安全
- 第三个参数可能不是有效的32位有符号整数
使用 testlib,你可以这样写:int a = opt<int>(3)
。
同时,你也可以这样:long long b = opt<long long>(2)
;bool f = opt<bool>(2)
;string s= opt(4)
;
如果你有很多参数需要输入,执行命令类似于这样:g 10 20000 a true
,那么将其改成这样会更具有可读性: g -n10 -m200000 -t=a -increment
。
在这种情况下,你可以在 generator 中使用如下方法获取参数
int n = opt<int>("n"); long long n = opt<long long>("m"); string t = opt("t"); bool increment = opt<bool>("increment");
编写命名参数的方案有如下几种:
-key=value
、--key=value
-key value
、--key value
。这种情况下,value不能以-
开头--k12345
或-k12345
——如果 keyk
是一个字母,且后面是一个数字;-prop
或--prop
——启用 bool 属性。
g1 -n1 g2 --len=4 --s=oops g3 -inc -shuffle -n=5 g4 --length 5 --total 21 -ord
一些示例
下面的例子均来自官方示例:testlib/generators at master · MikeMirzayanov/testlib (github.com)
1. 生成随机整数
// igen.cpp #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); cout << rnd.next(1, 1000000) << endl; return 0; }
如果你运行上述程序多次,每一次将会得到相同的结果。如果想生成不同的结果,可以在执行程序时,加入不同的参数。例如分别运行igen.exe 1
与 igen.exe 3
,将会产生不同的结果。testlib通过传入的参数来设置随机数种子。可以将此方法运用到后面的例子中。
2. 生成指定范围内的不等分布随机整数
//iwgen.cpp #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); cout << rnd.wnext(1, 1000000, opt<int>(1)) << endl; return 0; }
通过参数指定生成的数字是偏大还是偏小,iwgen.exe 1000
将产生偏大的数字,iwgen.exe -1000
将产生偏小的数字。
3. 生成多组测试数据
以下内容引用自:Testlib——最强出题辅助工具库
有两种方法可以一次性生成多组数据:
- 写一个批处理脚本。
- 使用 Testlib 内置的
startTest(test_index)
函数。
第一种方法非常简单,只需设好参数,将输出重定向到指定输出文件即可。
gen 1 1 > 1.in gen 2 1 > 2.in gen 1 10 > 3.in
对于第二种方法,在每生成一组数据前,调用一次 startTest(test_index)
,即可将输出重定向至名为 test_index
的文件。
//multigen.cpp 生成10组100以内的数字对 #include "testlib.h" #include <iostream> using namespace std; void writeTest(int test) { startTest(test); cout << rnd.next(1, test * test) << " " << rnd.next(1, test * test) << endl; } int main(int argc, char* argv[]) { registerGen(argc, argv, 1); for (int i = 1; i <= 10; i++) writeTest(i); return 0; }
4. 生成字符串
//sgen.cpp 生成 random token #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); cout << rnd.next("[a-zA-Z0-9]{1,1000}") << endl; return 0; }
其中 [a-zA-Z0-9]
指定了生成的字符串中所包含的字符,{1,1000}
指定了生成的长度范围。关于类似的写法规范可以参考:Testlib极简正则表达式 - OI Wiki (oi-wiki.org)
在下面这个例子中,可以使用参数控制生成字符串的长度范围。
//swgen.cpp #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); int length = rnd.wnext(1, 1000, opt<int>(1)); cout << rnd.next("[a-zA-Z0-9]{1,%d}", length) << endl; return 0; }
如果灵活使用参数,可以指定构造字符串。
下面这个程序通过参数给定的结构来输出字符串。
Examples: gs 1 4 ab => abababab gs 2 5 a 1 b => aaaaab gs 3 1 a 5 b 1 a => abbbbba
// gs.cpp #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); string t; int n = opt<int>(1); for (int i = 2; i <= 1 + 2 * n; i += 2) { int k = opt<int>(i); string s = opt<string>(i + 1); for (int j = 0; j < k; j++) t += s; } println(t); }
5. 生成一棵树
下面是生成一棵树(无根树)的主要代码,它接受两个参数——顶点数和伸展度。例如,当 时,可能会生成链;当 时,可能会生成菊花。
#include "testlib.h" #include <bits/stdc++.h> #define forn(i, n) for (int i = 0; i < int(n); i++) using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); int n = opt<int>(1); int t = opt<int>(2); vector<int> p(n); forn(i, n) if (i > 0) p[i] = rnd.wnext(i, t); printf("%d\n", n); vector<int> perm(n); forn(i, n) 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); return 0; }
如果想要生成一颗无根树,你可以:
#include "testlib.h" #include <bits/stdc++.h> #define forn(i, n) for (int i = 0; i < int(n); i++) using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); int n = opt<int>(1); int t = opt<int>(2); vector<int> p(n); forn(i, n) if (i > 0) p[i] = rnd.wnext(i, t); printf("%d\n", n); vector<int> perm(n); forn(i, n) perm[i] = i; shuffle(perm.begin() + 1, perm.end()); vector<int> pp(n); for (int i = 1; i < n; i++) pp[perm[i]] = perm[p[i]]; for (int i = 1; i < n; i++) { printf("%d", pp[i] + 1); // 输出 2 到 n 的每个父亲 if (i + 1 < n) printf(" "); } printf("\n"); return 0; }
6. 生成一个二分图
#include "testlib.h" #include <bits/stdc++.h> #define forn(i, n) for (int i = 0; i < int(n); i++) using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); int n = opt<int>(1); int m = opt<int>(2); size_t k = opt<int>(3); int t = rnd.next(-2, 2); set<pair<int,int> > edges; while (edges.size() < k) { int a = rnd.wnext(n, t); int b = rnd.wnext(m, t); edges.insert(make_pair(a, b)); } vector<pair<int,int> > e(edges.begin(), edges.end()); shuffle(e.begin(), e.end()); vector<int> pa(n); for (int i = 0; i < n; i++) pa[i] = i + 1; shuffle(pa.begin(), pa.end()); vector<int> pb(m); for (int i = 0; i < m; i++) pb[i] = i + 1; shuffle(pb.begin(), pb.end()); println(n, m, e.size()); forn(i, e.size()) println(pa[e[i].first], pb[e[i].second]); return 0; }
本文作者:kpole
本文链接:https://www.cnblogs.com/1625--H/p/15036991.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2020-07-20 2020牛客暑期多校训练营(第四场) C - Count New String (字符串,广义后缀自动机,序列自动机)