对拍脚本

1 什么是对拍

2 简单对拍模板

#!/bin/bash

for i in {1..100}; do
	printf "\r$i"
	./r >in # 数据生成
	./1 <in >out1
	./2 <in >out2
	diff -b out1 out2 || exit 1
done
说明

比较用 diff -b:忽略行末空格、行内空格数量差异,以及“少一个结尾换行 / CRLF 换行”这类差异。 这三种都是对拍里的常见假报错,sed/粘贴/Windows 编辑器都会意外造出来。 它仍然能认出 1 2 变成 12 这种 token 边界变化,所以不是把格式检查关掉了。

2.1 如何执行

这个脚本默认同目录下有三个程序:r 是数据生成器、1 是被测程序、2 是标准答案。把它们编译好之后:

  1. 删除第一行的 #!/bin/bash
  2. 保存成 dp.sh(对拍的拼音首字母),放在和 r、1、2 同一个目录
  3. 在终端执行:
bash dp.sh
注意

为什么要把 #!/bin/bash 删掉?因为在 Windows 编辑器里复制粘贴时,这一行很容易变成 CRLF 换行(末尾多个 \r), 一旦你习惯性地写 ./dp.sh 去执行,就会报 /bin/bash^M: bad interpreter: No such file or directory。 既然统一用 bash dp.sh 显式调用解释器,shebang 就只是普通一行注释,留着只会误导,不如删掉。

输出里先是一串数字进度(printf "\r$i" 原地刷新),跑到有差异的那组时,diff 会把两边答案打出来, 同时目录里留着 in、out1、out2 三个文件,可以直接 cat in 看反例,不需要脚本替你打印。

3 复杂对拍模板

功能:

  • 自动编译相应文件
#!/bin/bash
total_count=200
compare_dir="compare"
data_code=data.cpp
usr_code=1.cpp
std_code=right.cpp

usr_program="${usr_code%.*}.out"
std_program="${std_code%.*}.out"
data_generator="${data_code%.*}.out"

newer_than_compile() {
	local code=$1
	local out=$2
	if [[ $code -nt $out ]]; then
		echo "$code newer than $out Compile ..."
		b --no_debug $code -o $out --not_in
		if [ $? -ne 0 ]; then
			exit 1
		fi
	fi
}

newer_than_compile $usr_code $usr_program
newer_than_compile $std_code $std_program
newer_than_compile $data_code $data_generator

# 比较的基本路径
# tmp 是一个在内存的中filesystem,速度快
base_dir=/tmp

show_progress() {
	local total=$1
	local current=$2
	local width=50 # 进度条宽度

	# 计算进度百分比
	local percentage=$((current * 100 / total))
	# 计算进度条填充的长度
	local progress=$((current * width / total))
	# 生成进度条字符串
	local bar=$(printf "=%.0s" $(seq 1 $progress))
	# 显示进度条和百分比
	# printf "\r[%-${width}s] %d%%" "$bar" "$percentage"
	printf "\r[%-${width}s] %d/%d %d%%" "$bar" $current $total $percentage
}

mkdir -p $compare_dir

show_progress $total_count 0
for ((i = 1; i <= total_count; i++)); do
	# ./$data_generator > $compare_dir/in
	# ./$usr_program < $compare_dir/in > $compare_dir/user_out 2> /dev/null
	# ./$std_program < $compare_dir/in > $compare_dir/std_out 2> /dev/null

	./$data_generator >$base_dir/in
	./$usr_program <$base_dir/in >$base_dir/user_out 2>/dev/null
	./$std_program <$base_dir/in >$base_dir/std_out 2>/dev/null
	if ! /usr/bin/diff -b -q $base_dir/user_out $base_dir/std_out &>/dev/null; then
		# 如果diff出错
		echo # 换行
		echo "diff出错,i的值为 $i"
		vimdiff $base_dir/user_out $base_dir/std_out # 执行vimdiff进行比较
		mv $base_dir/in $compare_dir
		rm $base_dir/user_out $compare_dir
		rm $base_dir/std_out $compare_dir
		exit 1
	fi
	show_progress $total_count $i
done
rm $base_dir/user_out $base_dir/std_out

4 check脚本

如果你有一个data文件夹,里面有很多的数据,你需要检查你的代码1.out能否通过所有的数据,怎么办?

./data
├── problem0.in
├── problem0.out
├── problem1.in
├── problem1.out
├── problem2.in
├── problem2.out
├── problem3.in
├── problem3.out
├── problem4.in
├── problem4.out
├── problem5.in
├── problem5.out
├── problem6.in
├── problem6.out
├── problem7.in
├── problem7.out
├── problem8.in
├── problem8.out
├── problem9.in
└── problem9.out

1 directory, 20 files

可以使用这个脚本,

TODO : 添加timeout.

#!/bin/bash

# 使用for循环列出所有的*.in文件
for file in data/*.in; do
	./1.out <$file > out
	output_file="${file/.in/.out}"
    # if diff out $output_file -b then;
	if diff -b -q out $output_file; then
		echo "Test case $file passed!"
	else
		echo "Test case $file failed!"
	fi
done