michimani.net

C++ で AtCoder やり始めたのでローカル環境の構築方法をメモしておく

2023-12-18

今年の 10 月から AtCoder で競プロを始めました。初めてから 2 ヶ月、コンテストに参加したり過去問をやっていたりする中で、その場では解けてもあとからコードを見て何をやっているかわからない という状況が発生することが多いと気づきました。なので、コンテスト・過去問問わず自分が解いた問題については未来の自分がわかるように解説を書いておこうと思った次第です。

初回の今回は、とりあえずローカル環境の構築方法について書いておきます。

AtCoder への参加状況

現在の参加状況は以下の通りです。

ローカル環境

下記のマシン、環境で解いています。

実際の AtCoder での実行環境と異なっているのですが、一旦このままで始めています。

解答の管理

ローカル環境では、 下記のようなディレクトリ構成で解答を管理しています。

./
├── abc
│   ├── 001-100
│   │   ├── 001
│   │   │   └── cpp
│   │   │       ├── a.cpp
│   │   │       ├── b.cpp
│   │   │       
│   │   ├── 002
│   │   │   └── cpp
│   │   ...
│   ├── 101-200
│   │   ...
│   ├── 201-300
│   │   ...
│   └── 301-400
│       ...
├── arc
│   ├── 001-100
│   └── 101-200
└── onlinejudge
    └── test
        └── ...

新たに解答を作成する場合は、 new.sh として下記のようなシェルスクリプトを用意しています。

#!/bin/bash

set -eu

contest_type=$1
contest_number=$2

# contest_type must be abc, arc or agc
if [[ "${contest_type}" != "abc" && "${contest_type}" != "arc" && "${contest_type}" != "agc" ]]; then
	echo "contest_type must be abc, arc or agc"
	exit 1
fi

# contest_number must be positive integer
if [[ ! "${contest_number}" =~ ^[0-9]+$ ]]; then
	echo "contest_number must be positive integer"
	exit 1
fi

# contest_range is used for directory name
if [[ "${contest_number}" -ge 401 ]]; then
	contest_range="401-500"
elif [[ "${contest_number}" -ge 301 ]]; then
	contest_range="301-400"
elif [[ "${contest_number}" -ge 201 ]]; then
	contest_range="201-300"
elif [[ "${contest_number}" -ge 101 ]]; then
	contest_range="101-200"
else
	contest_range="001-100"
fi

# contest_number is used for directory name. 001, 002, ...
contest_number=$(printf "%03d" "${contest_number}")

# contest_name is used for README.md
if [[ "${contest_type}" == "abc" ]]; then
	contest_name="AtCoder Beginner Contest"
elif [[ "${contest_type}" == "arc" ]]; then
	contest_name="AtCoder Regular Contest"
elif [[ "${contest_type}" == "agc" ]]; then
	contest_name="AtCoder Grand Contest"
fi

# create directory
mkdir -p "./${contest_type}/${contest_range}/${contest_number}/cpp"

# create a, b and c files
touch "./${contest_type}/${contest_range}/${contest_number}/cpp/a.cpp"
touch "./${contest_type}/${contest_range}/${contest_number}/cpp/b.cpp"
touch "./${contest_type}/${contest_range}/${contest_number}/cpp/c.cpp"
touch "./${contest_type}/${contest_range}/${contest_number}/README.md"

# update README.md
readme_content="\
${contest_name} ${contest_number}
===

[問題 - ${contest_name} ${contest_number}](https://atcoder.jp/contests/${contest_type}${contest_number}/tasks)"

echo "${readme_content}" >"./${contest_type}/${contest_range}/${contest_number}/README.md"

例えば、 ABC 123 への解答を作成する場合は、下記のように実行します。

./new.sh abc 123

online-judge-tools の導入

問題を解いているときに例題に対するテストを簡単に実行するために、 online-judge-tools 1 を導入しています。

online-judge-tools は下記のような構成で利用しています。

  1. 例題を取得するシェルスクリプトを用意
  2. VSCode のタスクとして、実装中のファイルに対して例題を取得してテストを実行する

1. 例題を取得するシェルスクリプトを用意

まず、 online-judge-tools をインストールします。

pip install online-judge-tools

次に、例題を取得するスクリプトを用意します。

#!/bin/bash

## target directory to problem name
## ex) abc/001-100/001/cpp -> abc001
target_problem_dir=$1
IFS='/' read -ra ADDR <<< "${target_problem_dir}"
problem_name="${ADDR[0]}${ADDR[2]}"
problem_number=$2
test_dir="onlinejudge/test/${problem_name}_${problem_number}"
base_url=${problem_name}
code_path=$3
ac_url="https://atcoder.jp/contests/${base_url}/tasks/${problem_name}_${problem_number}"

# make test directory
if [ ! -e "${test_dir}" ]; then
    oj dl -d "${test_dir}" "${ac_url}"
fi

# C++ 20 (Clang 16.0.6) compile
# https://atcoder.jp/contests/APG4b/rules?lang=ja
clang++ -std=c++20 \
-Wall \
-Wextra \
-O2 \
-DONLINE_JUDGE \
-DATCODER \
-mtune=native \
-march=native \
-fconstexpr-depth=2147483647 \
-fconstexpr-steps=2147483647 \
-I/opt/boost/clang/include \
-I/opt/ac-library \
-I/usr/include/eigen3 \
-o ./a.out "${code_path}" \
&& oj test -c "./a.out " -d "${test_dir}"

このスクリプトは引数として下記の値を受け取るようになっています。

それぞれ、次に示す VSCode の task.json から渡されます。

受け取った値をもとに、 online-judge-tools の oj dl コマンドで例題を取得し、 oj test コマンドでテストを実行しています。

2. VSCode のタスクとして、実装中のファイルに対して例題を取得してテストを実行する

VSCode のビルドタスクとしてテストを実行できるように、下記のような JSON を .vscode/tasks.json として作成します。

{
  "tasks": [
    {
      "type": "shell",
      "label": "test_atcoder_sample",
      "command": "${workspaceFolder}/onlinejudge/cpp-test.sh",
      "args": [
        "${relativeFileDirname}",
        "${fileBasenameNoExtension}",
        "${file}"
      ],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ],
  "version": "2.0.0"
}

環境にも依りますが、上記の task.json を用意すれば Command + Shift + B で実装中のコードでテスト実行できるようになります。


以上がローカル環境の構築方法でした。

これから

冒頭にも書きましたが、今後は自分が解いた問題について解説を書いていこうと思います。

実際に解いた問題については michimani/atcoder に追加しています。


  1. online-judge-tools/oj: Tools for various online judges. Downloading sample cases, generating additional test cases, testing your code, and submitting it.  ↩︎


comments powered by Disqus