paiza のような標準入出力を使うコーディングテストで制限時間ギリギリ使って解くための1つの方法


この頃流行りの釣りタイトル大変申し訳ございません。

プログラミングコンテストというか、標準入出力を使うスキルテスト系の問題にチャレンジする時、テストの際にいちいち echo なり cat してパイプでつないで結果を diff して・・・って、面倒ですよね。しかもいちいちテストケースや結果を別ファイルに保存していたらたった数十分のためにファイルの作成の手間も、終わった後のファイルの管理も大変。
そんなあなたのために、ソースコードにコメントでテストケースを入れることで簡単にテストができるシェルスクリプトを作ってみました。

自分の環境はgo言語、OSXですが、複数行コメントが使える言語で、sh が動く環境ならどこでも使えるんじゃないかと思っています。突っ込み大歓迎です。
あ、コンパイルしないと動かない言語の人たちはすいません・・・。(改造すれば使えなくはないかも)

使い方(先に例を見た方が早いかも)

  • 下記の test.sh を適当なディレクトリに保存して、 chmod u+x test.sh
  • 同じディレクトリにソースコードを保存。その際、
    • 複数行コメントのなかに、-- COMMAND -- で始まる行を追加、その直後の行にプログラムを実行するためのコマンド(go runとか)を記載(ソースコードのファイル名は自動で付加するので不要)
    • -- INPUT --で始まる行を追加、その後に標準入力で突っ込むべきインプットを記載(複数行可。)入力の終わりは -- で始まる行で指定。(次の -- OUTPUT -- と兼用可)
    • -- OUTPUT --で始まる行を追加、その後に標準出力で返ってくるべきアウトプットを記載(複数行可。)入力の終わりは -- で始まる行で指定。
  • ./test.shと叩くと、カレントディレクトリ内の最新ファイルを実行し、結果が出てくる。

ソースコード

GitHub にも入れてあります。☆頂けると泣いて喜びます。

test.sh
#! /bin/sh

## 引数が1つ以外の場合はカレントディレクトリの最新ファイルを参照する
src=$1
if [ $# -ne 1 ]; then
  src=`ls -1t | grep -v test.sh | head -n 1` 
fi

## 実行コマンド(1行の前提)
runcmd=`cat $src | grep -A1  "^-- COMMAND --" | tail -1`
runcmd="$runcmd $src"
## 入力データ開始位置
inputbegin=`cat $src | grep -n  "^-- INPUT --" | sed -e 's/:.*//g' -e '2,$d'`
inputbegin=$[$inputbegin+1]
## 入力データ終了位置(開始からの行数)
inputlineno=`cat $src | tail -n +${inputbegin} | grep -n  "^--" | sed -e 's/:.*//g' -e '2,$d'`
inputlineno=$[$inputlineno-1]
inputend=$[$inputbegin+$inputlineno-1]

## 入力データ
inputdata=`cat $src | sed -n -e ${inputbegin},${inputend}p`

## 想定出力データ開始位置
outputbegin=`cat $src | grep -n  "^-- OUTPUT --" | sed -e 's/:.*//g' -e '2,$d'`
outputbegin=$[$outputbegin+1]
## 想定出力データ終了位置(開始からの行数)
outputlineno=`cat $src | tail -n +${outputbegin} | grep -n  "^--" | sed -e 's/:.*//g' -e '2,$d'`
outputlineno=$[$outputlineno-1]
outputend=$[$outputbegin+$outputlineno-1]

## 想定出力データ
outputdata=`cat $src | sed -n -e ${outputbegin},${outputend}p`

## 実行結果
result=`echo "$inputdata" | $runcmd`

## 結果表示
echo 実行コマンド : $runcmd
echo "\n実行結果"
echo "$result"
echo "\n想定実行結果"
echo "$outputdata"
echo "\n比較結果"
echo "$outputdata" | ( echo "$result" | diff /dev/fd/3 -) 3<&0 && echo "OK!"

## 一致しなければ diff がステータス1で終了。

※ 結果の比較には標準入力同士の diffを参考にさせていただきました。

利用例

paizaの練習問題 を例にとります。

practice.go
/*
-- TEST SCRIPT at https://github.com/kkoiwai
-- COMMAND --
go run
-- INPUT --
4
1000
1992
2000
2001
-- OUTPUT --
1000 is not a leap year
1992 is a leap year
2000 is a leap year
2001 is not a leap year

--
*/
package main
import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)
func main(){
    // 自分の得意な言語で
    // Let's チャレンジ!!
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan() 
    for scanner.Scan() {
        i, err := strconv.Atoi(scanner.Text())
        if err != nil {
            continue
        }
        if leap(i) {
            fmt.Println(strconv.Itoa(i) + " is a leap year")
        }else{
            fmt.Println(strconv.Itoa(i) + " is not a leap year")
        }
    }

}
func leap(n int) bool{
    // 西暦が4で割り切れない年は閏年ではない。
    if n % 4 != 0 {
        return false
    }
    // 100で割り切れ,400で割り切れない年は閏年ではない。
    if n % 100 == 0 && n % 400 !=0 {
        return false
    }
    return true
}

実行結果

$ ./test.sh 
実行コマンド : go run practice.go

実行結果
1000 is not a leap year
1992 is a leap year
2000 is a leap year
2001 is not a leap year

想定実行結果
1000 is not a leap year
1992 is a leap year
2000 is a leap year
2001 is not a leap year

比較結果
OK!

よかったら使ってみてください。

同志

[paiza]C#のテストを楽に行う方法
paizaで使えるテスト小ワザ(Ruby)
Paizaで使える標準入力の取得
[.NET] 競技プログラミングで解いたプログラムをローカル環境でテストしたい時に便利な裏ワザ