• Dropboxで公開されているテストケースをダウンロードしてくるスクリプトを書いた

はじめに

はじめまして. ゆーとす(@u_t0s)です.

最近,AtCoder流行ってますよね? 自分も,入社後から つよい人 を目指して精進しているところです.

ところでコンテストはもちろんですが, AtCoder Beginners Selectionなどにある過去問を解いていると, テストケースの中身が見たい と感じることも多いと思います,

そういった想いに応えるためか,AtCoder公式コンテンツとして,過去のコンテストのテストデータを見ることができるAtCoder’s Testcasesというものが用意されています.
こちらはDropboxの共有フォルダのリンクとなっているので,目的のデータを見つけてローカル環境でテストをすることができます.

ただ,目的のデータを探すのが少し手間で

  • どうしてもDropboxのリンクをたどるのが面倒
  • テストデータをダウンロードするのも一つ一つクリックして開きダウンロードする手間

など,なかなかスムーズにコードをテストするところまで進まないことがあります.

こういった手間を省くため,

で紹介されている3つの拡張機能のうち,AtCoder-TestCase-Extentionを使うと,直接データに飛ぶことができ非常に便利です. ですが,提出ページから一つ一つのデータをローカル環境にダウンロードするのは,やはり手間がかかります.

また,AtCoder補助ツールとして利用している

この oj コマンドでは,ローカルに用意した入力と出力のペアを用いてテストを実行できます
( 非常に便利です!!おすすめ!! ).

AtCoder’s Testcases と oj コマンド,この2つを組み合わせることでローカル環境ですべてのテストケースを試すことができます.

これにより,

  • インターネット環境がなくてもシステムテストができる
  • TLE になってしまう解法が実際にどれほど時間がかかるのかわかる(アルゴリズムの力を実感しました)
  • RE になる際のエラーメッセージを読むことができる(デバッグが少しは楽になります)
  • WA になるエッジケースや自分の見落としに気づける
  • すべてのテストケースが AC になることがわかった状態で提出できる(自分の提出結果がきれいに見えるだけです...)

といったメリットがあります.

これらを実行するためには,Dropbox上のテストデータをすべてダウンロードする必要があります. そこで,今回はShellscriptを用いてDropbox上のテストデータをダウンロードするものを書きました.

コード

拡張機能を開発されている方が

各テストケースへのURLを取得する必要があり,DropboxのAPIからサクッと取得できると楽だったのですが,ドキュメントを調べた限りそれは難しいようでした.

とおっしゃっているように,DropboxにもAPIが用意されているのですが,共有フォルダの情報取得は難しそうです. また,各コンテストのフォルダへのリンクもコンテストごとに異なるうえに,直接 curl でウェブページのソースを持ってきても動的に生成される部分があるらしく一部の情報しかとってこれません...

そこで,先のブログにある PhantomJs Cloud を利用しようとしたのですが,最近は Headless Chrome というのが便利なようなので,そちらを使用してJavaScriptで動的に生成した後の結果を取得します.


基本的には,

  1. 与えられたコンテスト名称が公開されているか調べる(ここで Headless Chrome を使用する)
  2. 公開されている場合,各問題ごと(ex. A,B,C,D)にテストデータをダウンロード
  3. ダウンロードしたテストデータのファイル名・拡張子を oj コマンドで使えるようリネーム

を行っています.

非常に困るのが,Dropboxのリンクは,ファイルへのパスが動的に変化するので,フォルダを1階層降りるたびに新しいファイルパスを取得しながら目的のフォルダに移動する必要があります. そのため,各テストケースへのリンクを得るために複数回の移動(動的に生成されるURLの取得とそのURL先の生成されたページの取得)を重ねます. (この部分が非常に冗長なので,こうした方が良いよ〜って教えてくれると嬉しいです!!)

#!/bin/bash
# @date Time-stamp: <2019-08-14 13:26:36 tagashira>
# @file dl_system_testcase.sh
# @brief AtCoder Testcase Downloader

set -ue

readonly dropbox_static="https://www.dropbox.com/sh/arnpe0ef5wds8cv/"
readonly UA="user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"

readonly path_to_cache="$HOME/atcoder/etc/"


function usage() {
  cat <<EOS
Usage:
  $0 COMMAND

  COMMAND      <contest_id>, update
EOS
  echo ""
  cat $0 | grep "@doc" |cut -d ' ' -f2- |tail -n +2

  exit 0
}


# @doc update testcase link in Dropbox folder
update_dropbox_link(){
  ping -c 1 google.com > /dev/null &&\
  google-chrome --headless --disable-gpu --dump-dom --virtual-time-budget=10000 "https://www.dropbox.com/sh/arnpe0ef5wds8cv/AAAk_SECQ2Nc6SVGii3rHX6Fa?dl=0" | sed -e "s#<#\n#g" -e "s#\\\#\n#g" -e "s#\"#\n#g" | grep $dropbox_static |sort -d |uniq > $path_to_cache/.dropbox_link.info && echo "Update Done"
}


check_testcase_exist(){
  local query=$1
  cat $path_to_cache/.dropbox_link.info | grep ${query^^} > /dev/null && echo "true" || echo "false"
}


obtain_contest_link(){
  local query=$1
  local isExist=$(check_testcase_exist $query)
  if [ $isExist = "true" ]
  then
    par_link=$(cat $path_to_cache/.dropbox_link.info | grep ${query^^})
    echo $par_link
  elif [ $isExist = "false" ]
  then
    echo "testcase doesn't exist OR wrong contest_id"
  fi
}


obtain_problem_link(){
  local contest_id=$1
  local par_link=$2

  curl -k -s -H 'accept-encoding: gzip, deflate, br' -H $UA -H 'content-type: application/json' $par_link --compressed |sed -e "s#<#\n#g" -e "s#\\\#\n#g" -e "s#\"#\n#g" | grep $dropbox_static |sort -d |uniq |grep "${contest_id^^}/"
}


dl_testcase_in_out(){
  local contest_id=$1
  local par_link=$2

  for problem_link in $(obtain_problem_link $contest_id $par_link); do
    for in_out_link in $(curl -k -s -H 'accept-encoding: gzip, deflate, br' -H $UA -H 'content-type: application/json' "${problem_link}" --compressed | sed -e "s#<#\n#g" -e "s#\\\#\n#g" -e "s#\"#\n#g" | grep $dropbox_static | grep -e "/out" -e "/in" | sort -d | uniq ); do
      local level=$(echo $in_out_link | cut -d '/' -f8 )
      mkdir -p system/${level,,}
      for dl_link in $(curl -k -s -H 'accept-encoding: gzip, deflate, br' -H $UA -H 'content-type: application/json' "${in_out_link}" --compressed | sed -e "s#<#\n#g" -e "s#\\\#\n#g" -e "s#\"#\n#g" | grep $dropbox_static | grep -e "/out/" -e "/in/" | sort -d | uniq); do
        local filename=$(echo $dl_link |cut -d '/' -f10 | cut -d '?' -f1)
        echo "$level/$filename"
        echo $dl_link | grep "/in/" > /dev/null && curl -sL $dl_link -o system/${level,,}/${filename}.in  &
        echo $dl_link | grep "/out/" > /dev/null && curl -sL $dl_link -o system/${level,,}/${filename}.out &
      done
      wait
    done
  done
}


# @doc <contest_id> download system test case
main(){
  if [[ $# -ne 1 ]]; then
    echo "parameter is wrong"
    echo ""
    usage
  elif [[ $1 = "update" ]]
  then
    update_dropbox_link
    exit 0
  fi

  local contest_id=$1
  local contest=$(echo $contest_id | tr -cd '[a-z]\n')
  local contest_num=$(echo $contest_id | tr -cd '0123456789\n')

  if [ -f $path_to_cache/.dropbox_link.info ]
  then
    :
  else
    update_dropbox_link || exit 1
  fi

  echo "Download ${contest^^}${contest_num} system testcase ..."
  obtain_contest_link $contest_id && dl_testcase_in_out $contest_id $(obtain_contest_link $contest_id)
}


main $@

※ コンテスト毎に命名規則が異なっていたりするようなので,対応できていないコンテストもありますが,その場合は諦めて手動ダウンロードです...

使用方法

readonly path_to_cache="$HOME/atcoder/etc/"

基本的にはこれで動くはずです,

  • path_to_cache にDropbox上にあるテストケースの情報をキャッシュするので,適当な場所にフルパスで指定
  • パスの通った場所にこのスクリプトを置く

初回のみ

dl_system_testcase.sh update

を実行してください.
※ 以降,新しいテストケースが追加されるたびに update を行ってください.

dl_system_testcase.sh <contest_id> # abc123, agc015

引数として, <contest_id> を与えると, テストケースが存在する場合にテストケースを ./system フォルダにダウンロードします.

以下のようにに各問題ごとにフォルダに分けて,入力には .in 出力には .out 拡張子を追加して保存してあるので,

system/
├── a
├── b
├── c
└── d

oj コマンドでテストケースのフォルダをしてしてあげればテストが可能となります.

oj test -d test/a -c ./a

oj コマンドのヘルプは各オプション(test,downloadなど)ごとにあります.

デモ

少し長いですが,ABC114のA問題のテストケースをダウンロードし, oj コマンドを用いてテストを行います.

dl_sys として スクリプトの symbolic link としています.

最後に

今回は,AtCoderで提出後に使用されるシステムテストケースをダウンロードするスクリプトを書きました.

独学なので,オレオレスクリプトですが,皆さんのお役に立てると幸いです.

バグの報告や,この部分をこうするとシュッとするなど意見ありましたら お手数ですが,下のコメントやTwitter/Githubまでお願いします!!

※Dropboxに何度もアクセスすることになります.攻撃にならないよう行儀よくスクリプトを使用しましょう