競プロ用オレオレ環境ジェネレータを作った

はじめに

この間、競プロ始めました。

去年の10月くらいから興味が出てきていたんですが(主にKaggleの人たちが競プロをやっているのを見て)なかなか腰が重くしばらく様子見していました。

最近、ちょっと暇ができたタイミングでちょうどAtCoderでABC120をやっていたので始めてしまった、というわけなんですがしばらくやってみて忙しくて手が回らさそうならやめてしまうかもしれません(と予防線だけ張っておく)。

とまあ、一回しかまだやっていないわけですが一回やってみて思ったのはAtCoderのサイトのエディタでやるのはちょっときついと(それはそう)。

そこで、手元の環境で競プロ用のコードをかけるように環境構築していたんですが、今回はその記録になります。

コード類は、

github.com

においてあります。

環境構築の詳細

実行環境

Docker使いました。

Dockerfileはこんな感じ。

FROM ubuntu:16.04
RUN apt-get update &&\
    apt-get install -yq software-properties-common

RUN add-apt-repository universe &&\
    apt-get update &&\
    apt-get install -yq g++ libboost-all-dev cmake make gdb &&\
    apt-get clean &&\
    rm -rf /var/lib/apt/lists/*
RUN mkdir /app
WORKDIR /app

CMD ["/bin/bash"]

出来るだけ実際の実行環境と同じような環境を作りたかったのでDockerを使うことにしたわけですが、やっぱりDocker便利ですね。

docker runコマンドも手打ちするのが面倒なのでmakeを使います。

Makefile(抜粋)はこんな感じ。

IMAGE := competitive:1.0

build: Dockerfile
    docker build -t ${IMAGE} .

env:
    docker run -v `pwd`:/app -it ${IMAGE} /bin/bash

環境ジェネレータとは

コンテストごとに、プログラムを書くディレクトリ構成を複製したかったので'環境ジェネレータ'です。

treeコマンドでジェネレートしたディレクトリ構成をみてみると

ABC119/
├── Makefile
├── README.md
├── build
├── cases
│   ├── a
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   ├── b
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   ├── c
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   │       ├── answer.txt
│   │       └── input.txt
│   └── d
│       ├── 0
│       │   ├── answer.txt
│       │   └── input.txt
│       ├── 1
│       │   ├── answer.txt
│       │   └── input.txt
│       └── 2
├── jobs.sh
└── src
    ├── a.cpp
    ├── b.cpp
    ├── c.cpp
    └── d.cpp

19 directories, 25 files

といった感じになります。

srcが各問題に対応したC++ソースファイル(当然ながら解法まではジェネレートしてくれません)が入り、buildはコンパイル済みオブジェクトが入ります。

casesには、それぞれの問題のテストケースが入ります(この部分はスクレピングで取ってきています)。

README.mdには問題の説明が入り、実行制御用のMakefileとjobs.shはテンプレートをコピーしてきています。

Makefileのテンプレート(抜粋)は

CXX := g++
ACPP := src/a.cpp

CFLAGS := -O2 -Wall -std=c++11 -DDEBUG
A := build/a.out

prob-a: ${A}
    ./jobs.sh ${A} a


${A}: ${ACPP}
    ${CXX} ${CFLAGS} ${ACPP} -o ${A}

みたいな感じになっています。make prob-aコンパイルから答え合わせまでやってくれます。

jobs.shの中身は

for f in 0 1 2; do \
  ./$1 < cases/$2/$f/input.txt > cases/$2/$f/output.txt; \
  diff cases/$2/$f/output.txt cases/$2/$f/answer.txt; \
done

となっています。input.txtの中身を標準入力で読んで、output.txtに標準出力で吐き、diffでanswer.txtとの差分を取って答え合わせをするという流れが書いてあります。

ジェネレート部分

ファイル生成はシェルで行います。

if [ ! -d $1 ]; then \
  mkdir $1; \
  cp Makefile.template $1/Makefile; \
  cp jobs.sh.template $1/jobs.sh; \
  mkdir $1/src $1/build $1/cases; \
  touch $1/build/.keep $1/cases/.keep; \
  for f in a.cpp b.cpp c.cpp d.cpp; do \
    touch $1/src/$f; \
  done; \
fi

あらかじめ書いておいたテンプレートをコピーして、所定の手続きでディレクトリやらファイルやらを作るだけです。

README.mdへの問題の書き出しとテストケースの取得はPythonで書いていますが、ちょっと長くなるので割愛します。

コンテストごとにそんなに問題の書き方は変わらないよね?という前提で処理しています。

まとめ

こんなことばっかりやっているので積みタスクが増えていくわけですが、競プロもできたら頑張りたいですね。