🐟

競プロスクリプト書いたよ

7 min read

こんにちは、atree です。二つ目の記事ですね。はてなブログと Zenn の兼ね合いに迷っています。

暇じゃない人

https://gist.github.com/atree-GitHub/a68ec9b790411fd9ef5c0782657489de
競プロ用のスクリプトを fish で書きました。
完全に自分用です。
oj/oj-apiの wrapper です。
おわり。

注意: 以下何も考えずにつらつらと書いているので文章のレベルが低いです。

前置き(ポエム)

自分は競プロを趣味としているのですが、その環境には以前から悩んでいました。環境といっても大きく、エディタ / IDEツールの 2 つです。
エディタ / IDE については、Sublime Text に始まり、人気の Visual Studio Code を使ってみると重くて Sublime Text に戻ってきたところで、今は人に勧められて Clion を使っています。(Clion なんか使って VSCode が重いとか言ってるやつは泡を吹いて倒れるんじゃないのか、という人、たぶん当時の自分は原義ナニモワカラナイ状態で(今もそうではあるが...)エディタの補完 etc に求めるものが大きすぎただけだと思います...)
ツールの使用について、競技プログラミングのためのツールについて深く書くことはしませんが、 Clion を使う前まではoj(online-judge-tools)acc(atcoder-cli)とともに使っていました。kymk さんを中心としたonline-judge-toolsコミュニティの思想もすきです。(突然の告白)

モチベーション(ポエム)

とにかく今は Clion を使っているのですが、問題点として、Clion は ojと共存しにくい気がします(純粋培養の技術力では)。Clion はガッツリ IDE してるので独立したツールを組み込んで使用するのは難しい、というわけです。そういうわけで自分はojaccを使わなくなりました。
ところでコンテスト中の動きを振り返ってみると、少なくとも今の自分(と現実世界においての周りの競プロ er)は一問ごとに Ctrl-Rでビルド->サンプルをクリックしてコピー->Ctrl-Vで貼り付け を何回か繰り返しています。
しかし!遅いです。遅い。 周りの er は強いしタイピングも速いのでいいかもしれませんが、カスコーダーである僕には面倒だし、時間を消費してるように感じられます。それでojを使いたくなってきたところでした。
ところで、今の自分の競プロディレクトリは、competitive/c++/コンテストサイト/コンテスト名/問題ごとのファイルという形になっていて、コンテストサイトごとにadd_executable(A abcXXX/A.cpp)などとあるCMakeLists.txtが置いてある、という状態になっています。この状態でaccを使うと都合が悪いです。accコンテスト名/問題/{main.cpp, test/sample_*.txt}の形でディレクトリを生やすのでこのままだと構成を変える必要が生じてしまいます。
これがなんとなく面倒で、こんなツイートをしたらこんなリプをいただいて、「確かに、自分はaccをディレクトリ作成でしか使ってないし、スクリプト書いてみてもいいな」という気持ちになりました。

本題

https://gist.github.com/atree-GitHub/a68ec9b790411fd9ef5c0782657489de
prefix は compro です(competitive programming)。自分は fish を使っているので fish スクリプトで雑に書きました。(と言っても純粋培養なので時間はかかりましたが...)四つの関数からなります。
以下軽く説明しますが、嘘かもしれません。

compro_setup.fish

function compro_setup
    argparse -n compro_setup 's/site=' -- $argv
    argparse -n compro_setup 't/no-testcase' -- $argv

    mkdir $argv[1]
    cd $argv[1]
    if not set -q _flag_t
        mkdir test
        mkdir target
    end

    if not set -q _flag_site
        set contest_url "https://atcoder.jp/contests/$argv[1]"
    else if test "$_flag_site" = "yukicoder"
        set contest_url "https://yukicoder.me/contests/$argv[1]"
    else if test "$_flag_site" =  "codeforces"
        set contest_url "https://codeforces.com/contest/$argv[1]"
    else
        echo 'Sorry, but that contest site is not supported.'
        return 1
    end
    touch data.json
    oj-api get-contest $contest_url > data.json
    or return 1

    echo -n -e "\n\033[36mSet up for "
    echo -n  (cat data.json | jq '.result.name' -r)
    echo -e "\033[m\n"

    set alphabet (cat data.json | jq -r '.result.problems[].context.alphabet')
    set name (cat data.json | jq -r '.result.problems[].name')
    set problem_url (cat data.json | jq -r '.result.problems[].url')

    for i in (seq 1 (count $alphabet))
        echo -ne "\n\033[35mSet up "
        echo -n {$alphabet[$i]} - {$name[$i]}
        echo -e "\033[m\n"
        cp $HOME/Documents/competitive/c++/template.cpp {$alphabet[$i]}.cpp
        if not set -q _flag_t
            mkdir -p test/{$alphabet[$i]}
            oj download $problem_url[$i] --directory test/{$alphabet[$i]}
        end
    end
    rm data.json
    echo -e "\n\033[33mDONE. DO YOUR BEST!\033[m"
end

Tips

  • オプションはargparseで生やせます。-s/--site=でコンテストサイトの指定(デフォルトでは AtCoder)、--no-testcaseでテストケースのダウンロードなしになるようになっています。
  • 引数は$argvに入っています。
  • oj-api get-contest <URL>でコンテストについての情報がJSONで返ってくるのでjqでパースします。oj-api偉すぎ!w
  • touch data.jsonしてるの良くないですね。あとで変数にしておきます。
  • エスケープでメッセージを色付きにできます。(c.f: 参考資料)
  • 自分が使うことしか想定していないのでテンプレートの path なんかはハードコードです。
  • ABC001の場合こんな感じのディレクトリ構成になります。
❯ exa --tree
abc001
├── A.cpp
├── B.cpp
├── C.cpp
├── D.cpp
├── target
│  ├── A.out
│  ├── B.out
│  ├── C.out
│  └── D.out
└── test
   ├── A
   │  ├── sample-1.in
   │  ├── sample-1.out
   │  ├── sample-2.in
   │  ├── sample-2.out
   │  ├── sample-3.in
   │  └── sample-3.out
   ├── B
   │  ├── sample-1.in
   │  ├── sample-1.out
   │  ├── sample-2.in
   │  ├── sample-2.out
   │  ├── sample-3.in
   │  └── sample-3.out
   ├── C
   │  ├── sample-1.in
   │  ├── sample-1.out
   │  ├── sample-2.in
   │  ├── sample-2.out
   │  ├── sample-3.in
   │  ├── sample-3.out
   │  ├── sample-4.in
   │  ├── sample-4.out
   │  ├── sample-5.in
   │  ├── sample-5.out
   │  ├── sample-6.in
   │  ├── sample-6.out
   │  ├── sample-7.in
   │  ├── sample-7.out
   │  ├── sample-8.in
   │  └── sample-8.out
   └── D
      ├── sample-1.in
      ├── sample-1.out
      ├── sample-2.in
      ├── sample-2.out
      ├── sample-3.in
      └── sample-3.out

ほか

function compro_compile
    echo -e "\033[35mCompiling...\033[m"
    compg++ {$argv[1]}.cpp -o target/{$argv[1]}.out
end
function compro_test
    echo -e "\033[35mTest...\033[m"
    oj test --command target/{$argv[1]}.out --directory test/{$argv[1]}
end
function compro_run
    echo -e "\033[35mRunning...\033[m"
    compro_compile $argv[1]
    or return 1
    compro_test $argv[1]
end

他3つははほぼエイリアスなので何も言うことがありません。強いて言うならコンパイルコマンドにはcompg++が使われています。(自分は$XDG_CONFIG_HOME/fish/config.fishalias compg++ "g++ -std=c++17 -g -Wall -Wextra -fsanitize=undefined -D_GLIBCXX_DEBUG"を置いています。)

あとがき

それなりに快適に競プロをできそうです。 fish / jq の勉強にもなったし、欲しいものがなさそうなら自分で作ってみてもいいの精神を学べました。たまにはいいですね。
自分はシェルスクリプト初心者なので、プロの方が見てくださってたら「こういう書き方の方がいいよ」とか教えてもらえるとめちゃめちゃ喜びます。
実はcomproコマンドにサブコマンドとしてrun compile testを生やす案もあったんですけどそこまでの余力はなかったのでまたの機会に、という感じです。(そこまでするんだったらシェルスクリプトよりも高級な言語でやりたい感はありますけどね、Rustclapとか良さそう(Rust書いたことないの顔))
こんな低レベルのもので記事まで生やしてすみませんでした...
なにはともあれ(強引)拙い文章最後まで読んでくださりありがとうございました!

参考資料

https://fishshell.com/docs/current/index.html
https://github.com/online-judge-tools/oj
https://github.com/online-judge-tools/api-client
https://github.com/Tatamo/atcoder-cli
https://qiita.com/ko1nksm/items/095bdb8f0eca6d327233

Discussion

ログインするとコメントできます