beet's soil

競プロのことなど

Rimeの使い方

最近有志コンが増えているので

この記事ではシェルとgitとpythonが動くことを前提としています。

はじめに

コンテストを開くためには問題とデータセットを作らないといけません。データセットを一から作るのは大変です。この記事ではデータセット作成の助けになるツールであるRimeの説明をします。
説明の通りに実行したサンプルをgithubに上げておきました。
github.com

複数人の場合の準備

複数人で準備する場合はgithub等を使うのが便利です。
当然関係者以外から見えるようになっているとまずいのでprivateレポジトリを作る必要があります。
学生ならgithub education等を利用できます。
qiita.com

人数が多くない場合はメールやslackでやり取りするのもありかもしれません。

Rimeのインストール

github.com
pythonとpipが動くなら、↑の通り

pip install git+https://github.com/icpc-jag/rime

でインストールできます。

Rimeの公式ドキュメントは↓
Rime — Rime 1.0 ドキュメント

作問ディレクトリの準備

適当な場所にディレクトリを作ります。github等を使うならcloneしておきます。
作業ディレクトリができたら

rime_init --git

でPROJECTファイルとcommonディレクトリが作成されます。
commonディレクトリの下にはtestlib.hが入っているはずです。

問題用ディレクトリの作成

rime add . problem <problem_dir_name>

で、problem_dir_nameというディレクトリが作成されます。例えばukukuという問題をつくるなら

rime add . problem ukuku

となります。上のコマンドを打つとPROBLEMファイルが開きます。

# -*- coding: utf-8; mode: python -*-

pid='X'

problem(
  time_limit=1.0,
  id=pid,
  title=pid + ": Your Problem Name",
  #wiki_name="Your pukiwiki page name", # for wikify plugin
  #assignees=['Assignees', 'for', 'this', 'problem'], # for wikify plugin
  #need_custom_judge=True, # for wikify plugin
  #reference_solution='???',
  )

atcoder_config(
  task_id=None # None means a spare
)

reference_solutionには想定解を指定できます。
想定解用のディレクトリを作ってからでないとエラーになるので注意。
まあ普通はtime_limitだけ設定できれば十分なはずです。

PROBLEMファイルはデフォルトだとvimで開かれますが環境変数EDITORを設定することで変更できます。

export EDITOR='emacs'

僕はemacsを使いたいので.bashrcに上の一文を追加しています。

解答用ディレクトリの作成

rime add <parent_problem_dir_name> solution <solution_dir_name>

で、parent_problem_dir_name下にsolution_dir_nameというディレクトリが作成されます。
たとえば先ほど作成したukukuがカレントディレクトリの時にbeetという解答用ディレクトリを作りたいなら

rime add . solution beet

となります。上のコマンドを実行するとSOLUTIONファイルが開きます。
ここで解答プログラムの名前を設定します。たとえばbeet.cppという名前にするなら

# -*- coding: utf-8; mode: python -*-

## Solution
#c_solution(src='main.c') # -lm -O2 as default
cxx_solution(src='beet.cpp', flags=[]) # -std=c++11 -O2 as default
#java_solution(src='Main.java', encoding='UTF-8', mainclass='Main')
#java_solution(src='Main.java', encoding='UTF-8', mainclass='Main',
#              challenge_cases=[])
#java_solution(src='Main.java', encoding='UTF-8', mainclass='Main',
#              challenge_cases=['10_corner*.in'])
#script_solution(src='main.sh') # shebang line is required
#script_solution(src='main.pl') # shebang line is required
#script_solution(src='main.py') # shebang line is required
#script_solution(src='main.rb') # shebang line is required
#js_solution(src='main.js') # javascript (nodejs)
#hs_solution(src='main.hs') # haskell (stack + ghc)
#cs_solution(src='main.cs') # C# (mono)

## Score
#expected_score(100)

となります。

解答プログラムの作成

先ほど作った解答用ディレクトリの下に書きます。
以降の説明では 二つの整数a,bを入力し、その和を出力する問題を考えます。
C++で実装すると以下のようになります。

#include<bits/stdc++.h>
using namespace std;
using Int = long long;
//INSERT ABOVE HERE
signed main(){
  int a,b;
  cin>>a>>b;
  cout<<a+b<<endl;
  return 0;
}

テスト用ディレクトリの作成

rime add <parent_problem_dir_name> testset <testset_dir_name>

で、parent_problem_dir_name下にtestset_dir_nameというディレクトリが作成されます。
たとえば先ほど作成したukukuがカレントディレクトリの時にtestsというテスト用ディレクトリを作りたいなら

rime add . testset tests

となります。上のコマンドを実行するとTESTSETファイルが開きます。
ここでgeneratorとvalidatorの設定をします。generatorは入力の生成を行い、validatorは入力の検証を行います。
validatorを用意しないと入力がバグっていて悲しいことになる可能性があるので必ず用意しましょう。
できれば複数人で独立に用意するべきです。

ここではgenerator.cppとvalidator.cppというファイル名にすることにします。

# -*- coding: utf-8; mode: python -*-

## Input generators.
#c_generator(src='generator.c')
cxx_generator(src='generator.cpp', dependency=['testlib.h'])
#java_generator(src='Generator.java', encoding='UTF-8', mainclass='Generator')
#script_generator(src='generator.pl')

## Input validators.
#c_validator(src='validator.c')
cxx_validator(src='validator.cpp', dependency=['testlib.h'])
#java_validator(src='Validator.java', encoding='UTF-8',
#               mainclass='tmp/validator/Validator')
#script_validator(src='validator.pl')

## Output judges.
#c_judge(src='judge.c')
#cxx_judge(src='judge.cc', dependency=['testlib.h'],
#          variant=testlib_judge_runner)
#java_judge(src='Judge.java', encoding='UTF-8', mainclass='Judge')
#script_judge(src='judge.py')

## Reactives.
#c_reactive(src='reactive.c')
#cxx_reactive(src='reactive.cc', dependency=['testlib.h', 'reactive.hpp'],
#             variant=kupc_reactive_runner)
#java_reactive(src='Reactive.java', encoding='UTF-8', mainclass='Judge')
#script_reactive(src='reactive.py')

## Extra Testsets.
# icpc type
#icpc_merger(input_terminator='0 0\n')
# icpc wf ~2011
#icpc_merger(input_terminator='0 0\n',
#            output_replace=casenum_replace('Case 1', 'Case {0}'))
#gcj_merger(output_replace=casenum_replace('Case 1', 'Case {0}'))
id='X'
#merged_testset(name=id + '_Merged', input_pattern='*.in')
#subtask_testset(name='All', score=100, input_patterns=['*'])
# precisely scored by judge program like Jiyukenkyu (KUPC 2013)
#scoring_judge()

のようになります。generator,validatorは複数指定することが可能です。

generator / validatorの作成

Rimeではgeneratorとvalidatorを書く際にCodeForcesでも使われているtestlib.hというライブラリを使います。
github.com
詳しい使い方は公式のサンプル等を参照してください。

今回は以下のようになります。

20個のランダムケースを作るgeneratorなら

#include "testlib.h"
#include<bits/stdc++.h>
using namespace std;
using Int = long long;
const int MIN_A = 1;
const int MAX_A = 1000;
const int MIN_B = 1;
const int MAX_B = 1000;
signed main(int argc,char* argv[]){
  registerGen(argc, argv, 1);
  for (int t = 0; t < 20; t++) {
    ofstream of(format("02_random_%02d.in", t+1).c_str());
    of << rnd.next(MIN_A, MAX_A) << endl;
    of << rnd.next(MIN_B, MAX_B) << endl;
    of.close();
  }
  return 0;
}

制約はconstで宣言しておいてgeneratorとvalidatorで共有するのがいいと思います。

#include "testlib.h"
#include<bits/stdc++.h>
using namespace std;
using Int = long long;
const int MIN_A = 1;
const int MAX_A = 1000;
const int MIN_B = 1;
const int MAX_B = 1000;
signed main(int argc,char* argv[]){
  registerValidation(argc, argv);
  inf.readInt(MIN_A, MAX_A, "A");
  inf.readEoln();
  inf.readInt(MIN_B, MAX_B, "B");
  inf.readEoln();
  inf.readEof();
  return 0;
}

入力に余計な空白や改行が1つでもあるとエラーになるので注意してください。
空白はreadSpace, 改行はreadEoln, 整数はreadIntやreadLong, 文字列はreadTokenで読み込めます。
最後にreadEofが必要です。

テストの実行

ようやく準備が整ったのであとはテストです。
問題用ディレクトリに移動して

rime test

で全ての解答ディレクトリについてテストが行われます。
特定の解答ディレクトリだけテストを行いたい場合はtestの後ろにディレクトリ名をつけます

rime test beet

テストケースの書き出し

ジャッジサーバーにアップロードする際には問題ディレクトリで

rime pack

コマンドを実行します。rime-outディレクトリの下にatcoderという名前のディレクトリが作成され、その下のin/outに入力/出力ファイルが書き出されます。

サンプル

サンプルとしてACPC2017のレポジトリへのリンクを貼っておきます
github.com

まとめ

Rimeはすごい!
間違いや意見等があったらコメントください