0. はじめに
2年ほど前からAtCoderに参加しています。はじめた頃はVSCode上でKotlin
を使用していました。しかし、もともとXcode+Swiftでアプリを開発していたことと、AtCoderのジャッジシステムが新しくなったこともあって、昨年(2020年)4月からSwift
でAtCoderに参加しています。
AtCoderのコンテスト中、テストケースの実行やコードの提出は、AtCoderのコンテストページから/へのコピペで行っていましたが、『これら、自動化できるよね』ということで、今回の記事に至りました。
AtCoderに関する自動化については、下記2つの記事が大変参考になります。
Swift言語においても、VSCodeやコマンドラインでコンパル&実行するなら、上記の記事で十分ですが、今回はXcodeのコンソールアプリのプロジェクトを使い回すことを前提にしています。つまり、コンテストや問題ごとにソースファイルを分けずに、常に一つのXcodeプロジェクトで実施しようというものです。これがベストプラクティスとは言いませんが、自分はこの環境でAtCoderに参加したいということです。
1. 今回のゴール
Xcode+SwiftのコンソールアプリにてAtCoderの解答コードを作成し、サンプルテストケースの自動実行とAtCoderへのソースコードの提出を自動化すること。
2. 準備
前出の自動化記事を参考に、online-judge-tools
とatcoder-cli
をインストールしておきます。
3. 環境
a) Xcodeのコンソールアプリのプロジェクト
Xcodeで 新規プロジェクト(New → Project) → macOS → Command Line Tool と進み、「プロジェクト名」やプロジェクトの「格納先ディレクトリ」を指定して作成したプロジェクトは、標準入力/標準出力から入出力するコンソールアプリとなります。変更しない限りプロジェクト名=プログラム名です。
b) ソースファイルの場所の確認
上記で指定したプロジェクトフォルダ配下にmain.swift
として作成されます。このファイルにAtCoderの問題を解くコードを書きます(いわゆるコーディング)。
AtCoderへ提出するコードがこれになるのでフルパスをメモしておきます。
c) ビルドして生成されるバイナリファイルの場所の確認
Xcodeでビルドすると、実行ファイルが生成されます。このファイルの場所は、XcodeのプロジェクトナビゲーターのProducts
の下にあるプロジェクト名と同名の実行ファイルをクリックすると、フルパスを知ることができます。
またShow in Finder
を選べば、その場所のファインダーが開きます。
ローカル環境でテストするバイナリファイル(実行ファイル)がこれになるのでフルパスをメモしておきます。
XcodeでRun
(実行)すると、このプログラムが起動され、Xcode上のコンソールで入力や出力されます。もちろん、ターミナルを開き、この実行ファイルを直接起動することもできます。
4. 自動化
AtCoderの2つの自動化に対応します。
- テストの自動化
- ソースコードの提出
4.1 テストの自動化
AtCoderのコンテストでは各問題ごとに例題として数件のテストケースが提供されます。『この入力を与えると、こう出力されたら正解』といった例です。この例題の入力と出力をもとに、作成したプログラムの動作確認を自動で行うことが目的です。
これには前出のonline-judge-tools
(oj)を使用します。ojはターミナルから投入するコマンドです。
a) AtCoderへログイン
下記のコマンドを投入します。AtCoderへのログインは、コンテストごとに最初に一回実行しておきます。
$ acc login
? username: ユーザID
? password: パスワード
$ oj login -u ユーザID -p パスワード https://atcoder.jp/
b) テストケースのダウンロード
サブフォルダ作成とテストケースのダウンロードをまとめて行ってくれるacc new
を使用します。コマンドの引数にコンテストIDを指定します。
この例では、コンテストID:ABC190
$ acc new abc190
すると、カレントディレクトリに、以下のようにコンテストID
フォルダが作成され、その配下に問題タスクごとのテストケースをダウンロードして入力と出力ファイルが作成されます。
.
└── ABC190
├── a
│ └── tests
│ ├── sample-1.in
│ ├── sample-1.out
│ ├── sample-2.in
│ ├── sample-2.out
│ ├── sample-3.in
│ └── sample-3.out
├── b
│ └── tests
│ ├── sample-1.in
│ ├── sample-1.out
│ ├── sample-2.in
│ ├── sample-2.out
│ ├── sample-3.in
│ └── sample-3.out
├── c
│ └── tests
│ ├── sample-1.in
│ ├── sample-1.out
│ ├── sample-2.in
│ ├── sample-2.out
│ ├── sample-3.in
│ └── sample-3.out
├── contest.acc.json
├── d
│ └── tests
│ ├── sample-1.in
│ ├── sample-1.out
│ ├── sample-2.in
│ ├── sample-2.out
│ ├── sample-3.in
│ └── sample-3.out
├── e
│ └── tests
│ ├── sample-1.in
│ ├── sample-1.out
│ ├── sample-2.in
│ ├── sample-2.out
│ ├── sample-3.in
│ └── sample-3.out
└── f
└── tests
├── sample-1.in
├── sample-1.out
├── sample-2.in
└── sample-2.out
acc contest|tasks
コマンドでAtCoderのURLが取得可能です。
$ acc contest abc190
AtCoder Beginner Contest 190 https://atcoder.jp/contests/abc190
$ acc tasks abc190
A Very Very Primitive Game https://atcoder.jp/contests/abc190/tasks/abc190_a
B Magic 3 https://atcoder.jp/contests/abc190/tasks/abc190_b
C Bowls and Dishes https://atcoder.jp/contests/abc190/tasks/abc190_c
D Staircase Sequences https://atcoder.jp/contests/abc190/tasks/abc190_d
E Magical Ornament https://atcoder.jp/contests/abc190/tasks/abc190_e
F Shift and Inversions https://atcoder.jp/contests/abc190/tasks/abc190_f
c) テスト
これまでは準備で、やっとテストです。
下記コマンドで実行します。各問題のサブディレクトリに移動して実行します。例えば、A問題はディレクトリaを指定します。
$ cd コンテストID
$ cd a
$ oj test -d tests -c "実行ファイルパス"
結果は、下記の通り。それぞれのテストケースが自動でテストされて出力と照合してACとなっています。
実行ファイルパスは固定のため、環境変数SWIFTEXEに設定しています。
$ cd ABC190
$ cd a
$ oj test -d tests -c "$SWIFTEXE"
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[INFO] 3 cases found
time: illegal option -- f
usage: time [-lp] <command>
[WARNING] GNU time is not available: time
[INFO] sample-1
[INFO] time: 0.035278 sec
[SUCCESS] AC
[INFO] sample-2
[INFO] time: 0.009566 sec
[SUCCESS] AC
[INFO] sample-3
[INFO] time: 0.009297 sec
[SUCCESS] AC
[INFO] slowest: 0.035278 sec (for sample-1)
[SUCCESS] test success: 3 cases
4.2 提出の自動化
下記コマンドでソースコードを提出します。コマンドの引数で前述の問題ごとのURLを指定します。
この例では、
コンテストID:ABC190
問題ID:ABC190-A (A問題)
コマンドの引数の意味は、--no-guess:推測しない、-y:応答確認しない、--no-open:提出後にAtCoderのサイトを開かない、 -l:言語を指定、です。
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a "ソースファイルパス"
結果は以下の通り。
ソースファイル名は固定のため、環境変数SWIFTSRCに設定しています。
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a "$SWIFTSRC"
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[WARNING] cannot guess URL since the given file is not in the current directory
: : :
: : :
[INFO] chosen language: 4055 (Swift (5.2.1))
[WARNING] the problem "https://atcoder.jp/contests/abc190/tasks/abc190_a" is specified to submit, but no samples were downloaded in this directory. this may be mis-operation
[INFO] sleep(3.00)
: : :
: : :
[SUCCESS] result: https://atcoder.jp/contests/abc190/submissions/19897304
[INFO] save cookie to: /Users/USERNAME/Library/Application Support/online-judge-tools/cookie.jar
ソースコードの提出はできましたが、想定外のワーニングが出ています。カレントディレクトリにソースファイルが置かれていないことが原因のようです。
とは言え、今回の目的は問題ごとにソースファイルを分けないので、元のソースファイルをカレントディレクトリにコピーしてこのファイルを指定するようにしました。するとワーニングがなくなります。
$ cp $SWIFTSRC ./
$ oj submit --no-guess -y --no-open -l Swift https://atcoder.jp/contests/abc190/tasks/abc190_a main.swift
[INFO] online-judge-tools 11.1.1 (+ online-judge-api-client 10.8.0)
[INFO] read history from: /Users/USERNAME/Library/Caches/online-judge-tools/download-history.jsonl
[INFO] found urls in history: https://atcoder.jp/contests/abc190/tasks/abc190_a
[INFO] problem recognized: AtCoderProblem.from_url('https://atcoder.jp/contests/abc190/tasks/abc190_a'): https://atcoder.jp/contests/abc190/tasks/abc190_a
[INFO] code (13321 byte):
: : :
: : :
[SUCCESS] result: https://atcoder.jp/contests/abc190/submissions/19897431
[INFO] save cookie to: /Users/USERNAME/Library/Application Support/online-judge-tools/cookie.jar
例えば、問題Aから問題Bへの切り替えは、サブディレクトの移動だけですみます。
$ cd ../b
5. コマンドレス化(CUI化)
上手く実現できる方式がなかなか見つからないのでGUI化を見送って、上記までの種々のコマンドをシェルスクリプトに閉じ込めて1文字のキャラクタをタイプするだけで実行可能にしました。
$ . ~/atc コンテストID
スクリプトの中身は、①login実行、②環境変数等を設定、③acc new コンテストID
の実行、④下記コマンドのfunction定義、です。
コマンド | 意味 | 内容 |
---|---|---|
a | A問題を選択 | サブディレクトをA問題に切り替え |
b | B問題を選択 | サブディレクトをB問題に切り替え |
c | C問題を選択 | サブディレクトをC問題に切り替え |
d | D問題を選択 | サブディレクトをD問題に切り替え |
e | E問題を選択 | サブディレクトをE問題に切り替え |
f | F問題を選択 | サブディレクトをF問題に切り替え |
t | テスト | 選択中の問題のテスト |
s | 提出 | 選択中の問題のソースコードを提出 |
コンテスト終了後に、logoutは手動で実行します。
6. おわりに
前出記事の真似に過ぎませんが、実際のコンテストに数回参加し実践しましたが、超絶便利になりました。自画自賛ですが、AtCoderされている方にはぜひ自動化をお勧めします。
ジャッジ結果がモーダルで表示される、stanicさんが開発したプラグインも大変便利です。こちらを参照してください。⇒ AtCoderResultNotifier - AtCoderの提出結果を通知するUserScriptを作成しました
これを使うために、SafariからGoogle Chromeに変えました。 こちらのuserscriptsをSafariにインストールすれば、上記のプラグインがSafariでちゃんと動きました。AtCoderResultNotifierの「スクリプトをインストール」ではインストールできないのでちょっと手こずりました。
New Remoteを選び、Enter remote url:に
https://greasyfork.org/scripts/371225-atcoderresultnotifier/code/AtCoderResultNotifier.user.js
を入れてOK。ファイルが読み込まれたら、右下のSaveをクリック。
みなさんの参考になれば幸いです。以上
7. 追加情報(2021.12.26追記)
7.1 online-judge-tools
のアップデート
アップデートの案内に従ってコマンドを投入するだけです。
$ pip install --upgrade pip # pip自身のアップデート
$ pip3 install -U online-judge-tools
$ pip3 install -U online-judge-api-client
7.2 gnu-time
のインストール
$ oj test -d tests -c "$SWIFTEXE"
[INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.0)
[INFO] 2 cases found
+[WARNING] GNU time is not available: gtime
+[HINT] You can install GNU time with: $ brew install gnu-time
$
アップデート後、上記のようにgtime
のインストールの案内が出てきたのでgnu-time
をインストールしました。
$ brew install gnu-time
すると、テスト時のWARNINGが出なくなりスッキリしました。
$ oj test -d tests -c "$SWIFTEXE"
/Users/USERNAME/Library/Developer/Xcode/DerivedData/AtCoder-fbvzrmhzrmdoyxeaszbdjifcteet/Build/Products/Debug/AtCoder -d tests
[INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.0)
[INFO] 2 cases found
[INFO] sample-1
[INFO] time: 0.013775 sec
[SUCCESS] AC
[INFO] sample-2
[INFO] time: 0.010516 sec
[SUCCESS] AC
[INFO] slowest: 0.013775 sec (for sample-1)
[INFO] max memory: 2.188000 MB (for sample-1)
[SUCCESS] test success: 2 cases
$