为什么要开发这样一个工具
为什么要开发这样一个工具?当然首先是客户有这个需求。命制过在线算法题的老师们都知道,出题相对是比较简单的,但是生成测试数据是一个比较麻烦的工作,尤其是需要有大量的数据的时候。比如:
- 手动编写测试数据容易出错且耗时。
- 如果要给其他人发送大量的测试数据,无论哪种方式,都占用比较大的空间;
- 在题目开发过程中,约束条件经常会变化。使用生成器,我们可以轻松地修改约束,并在需要时重新生成测试数据。
我们原来的计划是在网上找一个开源的工具来修改一下提供给客户使用,但是我们发现这些开源工具要不太简陋,无法满足需求;要不太复杂,使用这些工具的学习曲线比较陡峭,可能会让很多老师望而却步,所以最后我们决定吸收这些工具的一些优点,开发一个具备图形界面的OJ随机测试数据生成器供大家使用。在此过程中,重点参考了这个开源库的基本语法,并对它进行了扩展和加强。另外还参考了以下这些优秀的开源库的思路,在此向他们致敬:
我们在开发的时候,针对以上的一些突出的痛点,提供了以下解决方案:
- 提供图形界面给用户,用户只需要通过输入一些参数以及设置一些配置即可生成需要的数据;
- 用户设置好的配置,可以导出为配置文件,这样,只要把几百字节的配置文件发给其他需要的用户,其他用户就可以通过这个配置文件在我们的生成器中生成数据,不再需要传递完整的、庞大的数据;
- 提供配置的编辑功能,可以方便高级用户更灵活更快捷地修改约束条件来生成不同的测试数据
- 对于我们的OJ用户,提供根据生成的测试数据输入,自动生成输出结果的功能:只需要提前设置好算法题对应的标准程序,即可在生成测试数据后,上载到云端,在我们的后台会自动根据标准程序和生成的测试数据输入,自动生成输出。
界面
上图是我们图形化OJ随机测试数据生成器的界面,主要把测试数据分成以下几种:
- 基础数据:这是最常用的数据,在这里可以生成指定范围的随机数,通过一些技巧,可以生成更丰富的数据,这一块内容也是这篇使用说明重点讲解的地方;
- 图数据:可以生成有向图、无向图和树等数据,这块因为总体比较复杂,不提供生成脚本,只能通过图形界面操作来生成;
- 集合数据:可以生成1维数组,多维数组(最多10维)、排列、组合等数据
- 字符串数据:可以生成随机字符串、模式匹配字符串、回文字符串、相似字符串等;
- 数学:可以生成方程式、质数、等比数列、等差数列、斐波那契数列等;
- 表:可以生成矩阵和数独数据;
- 日期时间:可以生成不同格式的日期和时间数据;
- 高级选项:在这里可以修改生成数据的脚本,对于熟悉生成脚本的用户,能更高效更灵活地生成数据,并且在这里可以导出和导入生成脚本配置文件。
使用方法
这里重点介绍“基础数据”部分的用法,其他数据,基本上来说你如果对自己的需要的数据需求很清楚的话,应该能很容易上手。If You Know, You Know,不再赘述。
下面是基础数据部分的界面截图。
我们来看一下各个配置的含义。
假设我们要为一个进行整数加法运算的算法题生成数据,它在一行上输入两个整数(整数范围为1-1000000),以空格隔开,我们可以这么来设置:
- 首先我们要设置整数范围,可以给它设置一个变量名称比如为n,然后取值范围最小值为1,最大值为1000000,点击“添加整数按钮”即可将限制添加到下面的当前配置中。如果你想两个数字一定不同,那么可以将“生成的数字互不相同”这个选项选上,这样能确保生成的数字相互都不同,否则是会生成随机数字,这些数字有可能会(部分)相同。
- 接着,因为我们这里是明确需要两个数据,所以我们可以在“添加重复输出”部分,在“重复次数”中填入数字2,表示会重复两次生成2个数据,重复的内容是从我们刚定义的变量n中去随机生成,它会在刚才定义的范围(1-1000000)之间随机生成数字。注意这里的变量n必须这样写:${n}。也可以在下面当前配置部分的“已定义变量”便签上点击,它会自动将对应变量填入到重复内容输入框。下面我们可以选择数据的输出格式,默认是“列格式”,表示所有生成的数据会以一列输出,比如:
对于我们这个例子来说,我们应该让这两个生成的数字在一行上,所以我们应该选择“行格式”,并且数据之间空格隔开。做好这些配置后,点击“添加重复输出”即可将重复次数设置到下面的“当前配置”中。12747
747656
经过上面的配置,现在我们可以得到这样的配置结果:
如果你对刚才的配置不满意,你可以在对应的约束列表或者重复列表上点击右键,在出现的菜单中选择“编辑约束”或者“编辑重复”来进行编辑。
做完这些配置后,就可以在右侧的“数据预览”中点击“生成”按钮来生成数据,如下图所示:
可以看到,它已经生成了两个随机数,我们可以将它下载下来,或者如果你是使用我们的OJ系统的话,可以直接点击“上载到云端”按钮直接上载到我们的服务器上,服务器将会根据预设的标准程序,自动生成输出结果,并作为这个测试用例的输出。这样,一个测试用例就设置完成了,你可以重复点击生成->上载到云端 来生成更多的测试数据。
高级选项和脚本基本语法
在“高级选项”选项卡中,我们可以看到有一个“完整脚本”的输入框,里面就是我们刚才通过图形界面设置后得到的生成数据的脚本:
constraint n int 1 1000000 distinct
repeat 2 ${n} row separator " "
这里除了变量和数字,都是我们定义数据的脚本所需要的关键字:
constraint
:用于定义一个约束(限制),可以定义所使用的数据类型(支持int/set/string三种),int
:数据类型,此处为整数,还可以是集合(set
)或者字符串(string
)- 1:起始值
- 1000000:结束值,注意这里的范围1-1000000,是[1,1000000)的关系,即包含1,不包含1000000;
distinct
:表示生成的数字互不相同repeat
:用于定义重复- 2:表示重复2次,即生成2个数字
- ${n}:生成的数字范围由变量n所定义
row
:以行格式输出,后面必须跟separator
这个关键字,用于定义一行数据之间用什么字符隔开,这里用的是一个空格;
除了上面提到的这些关键字外,还有以下几个关键字,我们以例子来说明:
constraint n int 1 1000000 distinct sorted descending
constraint a int 1 101
repeat 1 ${a}
repeat a ${n} row separator " "
上面的脚本,会生成1-100(含100)之间的随机个数的、范围在1-1000000随机数,这些随机数互不相同(由distinct限制),这些随机数是有顺序的(sorted),并且是以降序(descending)排列,如果升序(默认可以不写,或者写ascending),则以升序排列。下面是其中的一个随机结果:
27
950239 897768 888177 783012 781326 726513 697881 696853 667978 575105 542215 429178 377634 375715 355323 345115 271868 269290 251414 240062 193736 175051 162627 145410 107093 96509 23547
这里的27,对应的脚本是repeat 1 ${a}
,表示生成一个a变量定义的值,它的范围是1-100,然后,用这个27作为重复次数,生成27个随机数,随机数范围在1-1000000之间,并且以降序排列。
constraint n int 1 2
constraint s string 10 charset ABCDEFGHIJKLMNOPQRSTUVWXYZ
repeat n ${s}
上面这条语句,会生成一个长度为10的,包含大写字母的随机字符串。这里用到了关键字string
,表示这个约束的数据是字符串,后面跟着的10是字符串长度,而charset
用于约束所使用的字符。它会生成类似下面的数据:
ICCREAZBXR
字符串定义中也可以使用distinct
和 sorted
(以及配套的ascending
和descending
)来进行限制。
特殊变量_index
系统内置了一个特殊变量_index
,这个变量不用提前在限制中定义就可以直接在repeat中使用,它会从0开始按照升序生成一系列值,比如:
constraint n int 100 101
repeat n ${_index}
会生成从0开始到99的值:
0
1
2
3
...
99
它也可以和数字(不可以和其他变量)进行+-*/
运算,如:
constraint n int 100 101
repeat n ${_index+1}
上面脚本将会生成从1-100的一系列值。
以上就是对于我们的图形化OJ随机测试数据的一个说明,其他功能可以自己通过界面操作来生成,最灵活的部分已经通过上面的例子给大家解释了。
使用案例
下面通过例子来展示几个有趣/有用的用法,更多的用法用户可以自己灵活采用。部分案例来自这里。
生成SQL语句:
constraint 姓 set 赵 钱 孙 李
constraint 名 set 一 二 三 四
constraint value int 10 1000
constraint status int 0 2
repeat 10 INSERT INTO list (name, value, status) VALUES ("${姓}${名}", ${value}, ${status});
可以生成类似下面的SQL语句:
INSERT INTO list (name, value, status) VALUES ("赵二", 560, 0);
INSERT INTO list (name, value, status) VALUES ("赵四", 728, 1);
INSERT INTO list (name, value, status) VALUES ("李三", 277, 0);
INSERT INTO list (name, value, status) VALUES ("赵三", 457, 0);
INSERT INTO list (name, value, status) VALUES ("李二", 437, 1);
INSERT INTO list (name, value, status) VALUES ("孙三", 507, 1);
INSERT INTO list (name, value, status) VALUES ("孙四", 189, 1);
INSERT INTO list (name, value, status) VALUES ("赵四", 601, 1);
INSERT INTO list (name, value, status) VALUES ("钱三", 436, 0);
INSERT INTO list (name, value, status) VALUES ("赵一", 961, 1);
这里用到了关键字set,它可以用于定义一个集合,表示从有限的几个值随机选择。
生成一组数据
生成一组数据,可以用类似下面的脚本:
constraint n int 5 10
constraint row int 10 20
constraint min int 150 200
constraint max int 500 1000
constraint num int min max
repeat 1 ${n}
repeat group n
repeat 1 ${row} ${min} ${max}
repeat row ${num}
end group
它的含义是:
第一行是n,代表接下来有n组数据。每组数据的第一行有三个数row min max,代表接下来有row个数字,每个数字的取值是 [min, max)
上面脚本生成的数据类似于:
7
18 164 848
833
645
228
334
482
796
448
712
227
343
406
578
338
669
446
416
315
291
10 154 524
475
188
336
215
379
453
191
432
458
363
14 177 869
393
408
419
866
327
213
806
292
434
360
486
794
835
848
18 161 981
231
361
944
716
865
808
316
954
769
332
406
190
391
316
540
302
536
909
14 167 545
403
456
517
229
411
377
179
210
263
522
249
276
255
346
14 182 869
292
269
521
415
574
636
617
546
584
814
676
730
728
779
13 181 715
562
331
238
481
542
321
323
372
495
365
319
457
711
一组数据我们可以用repeat group...end group
来定义,repeat group
和end group
之间可以使用repeat
语句。
在变量约束中使用其他变量
在变量约束中,可以使用其他提前定义好的变量,甚至还可以使用+-*/
和()
运算符,比如:
constraint m int 1 100
constraint n int 2*m 200
还可以在字符串约束中的长度部分使用变量,生成随机长度的字符串,比如:
constraint n int 1 2
constraint m int 1 100
constraint s string m charset ABCDEFGHIJKLMNOPQRSTUVWXYZ
repeat n ${s}
将会生成一个长度在1-100之间的包含大写字母的随机字符串。
在重复次数中使用其他变量
在重复次数中,也可以使用其他变量,比如:
constraint m int 1 10
constraint a int 1000 10000
constraint b int 10000 100000
repeat m ${a}
repeat 2*m ${b}
它会生成m个千位数的随机数,同时生成2*m个万位数的随机数,比如:
2098
9189
98930
17360
99229
21520
可以看到,此时m的随机值是2,因此生成了2个千位数,那么2*m就是4,所以生成了4个万位数。
为了让更多的少儿编程教育者能更好地生成测试数据,我们除了把它集成到我们自己的OJ系统中外,也把它开放给所有用户使用,可以点击这里(少儿编程OJ随机测试数据生成器)来访问它。第一次打开的时候可能会比较慢,它需要从服务器下载相关的生成器、语法解释器等,请耐心等待一会,直到界面完全加载完成。
希望大家喜欢。