“对拍”方法在程序验证中的应用
2022-08-31谢玉韬徐剑李庆英
谢玉韬 徐剑 李庆英
摘要:在编程实践中,如何验证所写程序是否正确有很多方法,“对拍”是常用的一种。介绍了“对拍”的基本概念、应用场景,以两个数求和为例详细说明了“对拍”的过程,在此基础上使用Java语言开发了图形用户界面的判题工具,该工具利用对拍原理,基于给定多组样例数据,对用户提交的程序的正确性进行验证并给出反例,操作界面友好,操作方便,提高了程序验证的效率。
关键词:程序验证;对拍;Java语言;图形化工具
中图分类号:TP311 文献标识码:A
文章编号:1009-3044(2022)18-0045-03
开放科学(资源服务)标识码(OSID):
1 引言
对拍是验证人们写出的程序是否正确的常用方法,特别是在编程竞赛中,如果一个题目暴力算法容易写,高效算法拿不准时,常采用“对拍”检验高效算法的正确性,避免不必要的丢分[1-2]。本文介绍了对拍的概念、应用场景、具体实现过程[3-4],在此基础上基于Java语言[5]开发了图形用户界面的判题工具,操作界面友好,操作方便,提高了程序验证的效率。
2 “对拍”的基本概念
“对拍”,就是针对相同的输入数据,对两份程序的输出结果进行比对。其中一份是确定正确的程序(朴素算法实现或网络上搜索的Accepted的程序),另一份是自己写的正确性待验证的程序,通过“对拍”可以较容易地得出自己程序是否正确的结论,如果不正确,“对拍”发现的反例也有助于程序的修正。在后面“对拍过程”一节会详细介绍对拍的实现步骤。
3 “对拍”的应用场景
3.1 通过“对拍”进行程序检错
平时在Online Judge上提交程序时,自己的程序无法通过,但又无法获得测试数据,可以编写随机数据生成程序,生成大量输入数据,再找一份正确的程序和自己的程序“对拍”,从而找到反例,找出程序错误。
3.2 通过“对拍”进行算法验证
参加编程比赛时,当编写出效率更高、算法更优的程序,但无法确定程序算法正确性时,可以再写一份用朴素算法实现的程序(通常时间复杂度高,思路简单,不易出错),将两者对拍,以此判断“高效算法”正确与否。
3.3 通过“对拍”出题
自己出题时,测试数据的生成、“标程”的正确性验证都需要运用“对拍”的方法。
4 “对拍”的过程
“对拍”可分为代码准备、编写并执行对拍脚本或对拍程序等步骤,下面以Window环境为例,结合例题介绍“对拍”的过程。
4.1 代码准备
“对拍”是将大量测试数据分别交给两份程序运行,比对两份程序运行结果,并将结果输出的过程。因此在“对拍”之前,通常需要编写随机数据生成、朴素算法程序和无法确定对错的高分解法程序。下面以求两个1~100的整数的和的C++源码为例:
1)编写随机数据生成程序(gen.cpp)
#include
#include
#include
using namespace std;
//产生[0,n)的随机整数
int rand1(int n) {
return (long long)rand()*rand()%n;
}
//产生[x,y]的随机整数
int rand2(int x,int y) {
return rand1(y-x+1) + x;
}
int main() {
srand((unsigned)time(0));
cout<
return 0;
}
2)編写确定正确的程序(std.cpp)
#include
using namespace std;
int main() {
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}
3)编写自己的程序(my.cpp)
#include
using namespace std;
int main() {
int a, b;
cin >> a >>b;
if (a > 80 && b > 80) {
cout << a - b << endl;
} else {
cout << a + b << endl;
}
return 0;
}
在my.cpp中,当两个加数a和b都大于80时,输出了它们的差,结果显然是错误的,下面通过对拍找出该错误。
4.2 “对拍”过程及实现
在“对拍”之前,依次编译上述三个程序,得到三个可执行文件: gen.exe、std.exe和my.exe。然后编写脚本或C++程序,循环执行如下过程,达到“对拍”的目的:
Step1:運行gen.exe产生输入数据in.txt。
Step2:以in.txt作为输入运行std.exe产生输出数据stdout.txt。
Step3:以in.txt作为输入运行my.exe产生输出数据myout.txt。
Step4:比对stdout.txt和myout.txt内容是否一致。
下面用批处理脚本和C++程序两种方式实现上述步骤。
1)批处理脚本(duipai.bat)实现对拍
:loop
@echo off
gen.exe > in.txt
my.exe < in.txt > myout.txt
std.exe < in.txt > stdout.txt
fc myout.txt stdout.txt
if not errorlevel 1 goto loop
pause
上述脚本中,fc命令用于比较文件内容是否相同,相同时输出“找不到差异”,不同时会列出不同的内容便于比对。
双击脚本运行,当找到例外,即两份程序输出结果不同时,循环结束,得到图1所示结果:
此时in.txt的内容为:95 87,此时正确输出值为182,自测程序输出值为8,原因是两个数都大于80时,程序处理逻辑有误。
2)C++程序实现对拍
编写脚本需要熟悉其语法规则,而且不同操作系统脚本差别较大,为避免再学习新的语言规则或造成混淆,可以使用C++程序实现同样的功能。头文件cstdlib中提供了System函数,可用来执行系统命令。源码如下:
#include
#include
using namespace std;
int main() {
while(true) {
system("gen.exe>in.txt");
system("std.exe
system("my.exe
if(system("fc stdout.txt myout.txt")){
cout<<"wrong";
break;
}
}
return 0;
}
5 给定测试数据的程序验证工具实现
前面介绍了对拍原理及实现过程,程序的测试数据是随机生成的,下面考虑另一种情形:n组程序测试数据已经给出,如何判断程序是否正确?这种情形常用于以下场合:比赛后官方发布了测试数据但不提供在线判题入口;做书上题目时,通过作者提供的样例判断程序是否正确等。
将前面代码中输入数据和标准输出数据生成过程省略,不难得到实现思路,下面给出字符界面和图形界面两种实现。
5.1 字符界面实现(C++)
在n组测试数据给出的情况下,需要提供n的值、程序文件名称、样例文件主名、样例输出和待测试输出文件扩展名等,源码如下:
#include
#include
#include
#include
using namespace std;
int main() {
string programName,fileName,ansName,solName;
int n;
cout<<"输入测试数据组数:";
cin>>n;
cout<<"输入程序文件名称:";
cin>>programName;
cout<<"输入样例文件主名:";
cin>>fileName;
cout<<"正确程序的输出文件扩展名:";
cin>>ansName;
cout<<"待测试程序的输出文件扩展名:";
cin>>solName;
for(int i=1; i<=n; i++) {
ostringstream oss1,oss2;
oss1<
system(oss1.str().c_str());
oss2<<"fc "<
system(oss2.str().c_str());
}
return 0;
}
假设测试数据有10组,输入文件为add1.in,add2.in...,add10.in,輸出文件为add1.out,add2.out...,add10.out,则在上面代码中,n的值为10;程序文件是指待测试程序编译后的exe文件,如自己写的求和程序编译后为my.exe;输入样例文件主名为add,正确程序输出文件扩展名为.out,待测试输出文件扩展名自己设定,如.my,将它们放到同一个目录下。
5.2 图形界面实现(Java)
在上一节字符界面程序中,程序使用较为烦琐,表现在如下方面:源程序需要手工编译、输入数据较多、出现错误需要去测试文件中核对等。基于上述原因,使用Java语言设计了图形用户界面的验证工具,用户输入样例文件信息并直接在界面中输入源程序,提交后就可以直观地看到对拍结果,提高了验证效率,程序界面如图2所示:
程序实现思路与C++类似,核心步骤及源码如下:
1)编译源程序
FileWriter fw=new FileWriter("d:/duipai/1.cpp");
fw.write(jta1.getText());
fw.close();
String cmd = "g++ d:/duipai/1.cpp -o d:/duipai/1.exe";
Process pr1=Runtime.getRuntime().exec(cmd); // 执行编译指令
pr1.waitFor();
2)读取样例文件内容函数
public String rf(String fileName) throws Exception {
File f=new File("d:/duipai/"+fileName);
FileReader fr=new FileReader(f);
char[] cs=new char[5000];
int a=fr.read(cs);
String css=new String(cs,0,a);
fr.close();
return css;
}
3)文件比对
for(int i=1;i<=n;i++) {
String s11=rf("a"+i+".out").trim();
String s22=rf("a"+i+".my").trim();
if(s11.equals(s22)) {
jta2.append(i+":Accepted"+"\n");
}
else {
jta2.append(i+":Error.输入:"+rf("a"+i+".in")+";样例输出:"+s11+";你的输出:"+s22+"\n");
}
}
}
6 结束语
本文介绍了对拍工具在程序验证中的应用,首先介绍对拍的概念、应用场景、具体实现过程,在此基础上基于Java语言开发了给定测试数据的图形用户界面的判题工具,为给定样例数据判定程序正确性提供了方便。将该工具设计为线上使用是下一步考虑的工作。
参考文献:
[1] 李煜东.算法竞赛进阶指南[M].郑州:河南电子音像出版社,2018.
[2] 王亚峰.信息学竞赛解题过程研究[J].中小学电教(下),2011(2):152.
[3] 贾小军.C语言程序设计[M].北京:人民邮电出版社,2014.
[4] 李柯景.浅析C语言中的随机数问题[J].长春大学学报,2008,18(6):64-68.
[5] 张继军.Java程序设计[M].北京:中国水利水电出版社,2019.
【通联编辑:谢媛媛】