VSCodeでの競プロ環境構築 (C++)
以前の記事 では Julia での環境構築を紹介しましたが、私自身は基本的にC++で競プロをしているので、その説明をしようと思います。
以前の記事に幾らか追加したような内容になっています。
環境としてはWSLを想定していますが、そうでなくても殆ど問題ないと思います。
また、この記事ではWSLのインストールやVSCodeのインストール等については説明しません。
競プロ用ディレクトリの作成
適当な競プロ用のディレクトリ(contests
とか)を作成します。
特に言及しない限り、この記事内では競プロ用ディレクトリをカレントディレクトリとします。
g++ のインストール
clang++ などの他のコンパイラもありますが、競プロでは g++ が一番使われていると思うので、これをインストールします。
そもそも、既にインストールされている人が殆どだとは思いますが…(最初の方に入れるであろう build-essential パッケージに同梱されています)
sudo apt install g++
を実行します。
g++ がきちんとインストール出来ているかを確認するために以下のコマンドを実行します。
g++ --version
うまくいっていれば g++ のバージョン情報が表示されるはずです。
VSCode における C++ の設定
C/C++ Extension Pack のインストール
拡張機能拡張機能 C/C++ Extension Pack をインストールをします。
C++ は競プロくらいでしか使わない、という方は C/C++ Extension Pack に同梱されている C/C++ だけのインストールでも良いかもしれません。
IntelliSense の設定
コーディングの体験を良くするために IntelliSense の設定をします。
.vscode
ディレクトリ内に c_cpp_properties.json
ファイルを作成し、以下のように記述します。
{
"configurations": [
{
"name": "WSL",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/g++",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
フォーマッタの設定
ClangFormat を使ってコードの整形をできるようにしていきます。
.clang-format
ファイルを作成して、これ に従ってルールを作成していきます。
.clang-format の例
好みが分かれると思うので、自分で作成する方が良いと思います。
---
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Right
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterJavaFieldAnnotations: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
BreakStringLiterals: false
ColumnLimit: 88
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Regroup
IncludeCategories:
- Regex: ^"(llvm|llvm-c|clang|clang-c)/
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: ^(<|"(gtest|gmock|isl|json)/)
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: .*
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: (Test)?$
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: Indent
IndentGotoLabels: false
IndentPPDirectives: None
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
Language: Cpp
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PPIndentWidth: -1
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
QualifierAlignment: Right
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Both
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
AfterIfMacros: true
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: ForContinuationAndIndentation
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
作成したら .vscode
ディレクトリ内の .settings.json
に "C_Cpp.clang_format_style": "file"
を追記します。
また、保存時に整形されるようにすると便利なので .settings.json
に "editor.formatOnSave": true
も追記します。
これにより Ctrl
+S
で整形されるようになります。
{
"C_Cpp.clang_format_style": "file",
"editor.formatOnSave": true
}
online-judge-tools の設定
ここでは online-judge-tools/oj
と online-judge-tools/template-generator
の2つを導入します。
これらについての説明は、以下のURLを見てください。
- https://github.com/online-judge-tools/oj/blob/master/docs/getting-started.ja.md
- https://github.com/online-judge-tools/template-generator/blob/master/README.ja.md
導入方法についてもそれぞれの GitHub に書いてありますが、この記事でも一応記述しておきます。
これらのツールの導入のために pip
や pipenv
などを使えるようにしてください。
インストールについての説明は省略します。
また、この記事では pipenv
を使ってインストールするので、それ以外の場合は適宜読み替えてください。
oj のインストール
下記のコマンドでインストールします。
pipenv install online-judge-tools
oj でのログイン
ログインをしなければ提出などができないので、設定していきます。
pipenv run oj login https://atcoder.jp
上記のコマンドを入力すると、AtCoderでのユーザー名とパスワードを求められるので入力します。
template-generator のインストール
基本的にはここに従います。
下記のコマンドでインストールします。
pipenv install online-judge-template-generator
VSCodeにおけるonline-judge-toolsの設定
シェルスクリプトの作成
まず configs
ディレクトリを作成し、その中に submit.sh
という名前でファイルを作成します。
そのファイルに以下のように書きます。
#!/bin/bash
submit_file=$1 # relative file path (${service_name}/${contest_id}/${problem_id}/main.cpp)
service_name=${submit_file%%/*}
contest_id=$(basename ${submit_file%/*/*})
problem_id=$(basename ${submit_file%/*})
case "$service_name" in
"AtCoder" )
problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
;;
"Codeforces" )
problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
;;
esac
pipenv run oj s -y ${problem_url} ${submit_file}
その後 chmod 755 configs/submit.sh
を実行してパーミッションを変更します。
同じように configs
ディレクトリの中に dltest.sh
という名前でファイルを作成し、以下のように記述して、パーミッションを変更します。
#!/bin/bash
problem_dir=$1 # relative directory path (${service_name}/${contest_id}/${problem_id})
service_name=${problem_dir%%/*}
contest_id=$(basename ${problem_dir%/*})
problem_id=${problem_dir##*/}
test_dir=${problem_dir}/test
case "$service_name" in
"AtCoder" )
problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
;;
"Codeforces" )
problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
;;
esac
if [ ! -e ${test_dir} ]; then
pipenv run oj d -d ${test_dir} ${problem_url}
fi
テンプレート等の作成
configs/
ディレクトリに prepare.config.toml
を以下の内容で作成します。
これ に従って適当に追記すると良いです。
contest_directory = "{service_name}/{contest_id}"
problem_directory = "{problem_id}"
[templates]
"main.cpp" = "my_main.cpp"
そして ~/.config/online-judge-tools/template/
ディレクトリに my_main.cpp
を作成します。
これがテンプレートファイルになるので、使いやすいように変更してください。
これ に従ってテンプレートを作成すると、入出力まで自動で書かれたファイルが生成されます。
スタイル等にこだわりがなく、初期のままで良ければ、テンプレートファイルを作る必要はありませんので prepare.config.toml
をそのようにしてください。組み込みのテンプレートファイル main.cpp
では入出力まで自動で生成されるようになっています。
テンプレートの例(入出力自動化無し)
<%!
import os
import platform
from logging import getLogger
import onlinejudge_template.generator.cplusplus as cplusplus
import onlinejudge_template.generator.topcoder as topcoder
import onlinejudge_template.generator.hook as hook
%>\
<%
logger = getLogger(__name__)
data["config"]["indent"] = "\t"
data["config"]["scanner"] = "cin"
data["config"]["printer"] = "cout"
if platform.system() == "Linux" and "clang" not in os.environ.get("CXX", "g++"):
include = "#include <bits/stdc++.h>"
else:
include = "\n".join([
"#include <iostream>",
"#include <string>",
"#include <vector>",
])
hook.register_filter_command(["sed", "-e", "s/std:://g", "-e", r"s/'\\n'/endl/g"], data=data)
%>\
${include}
using namespace std;
% if topcoder.is_topcoder(data):
<% solve_function = topcoder.class_name(data) + "()." + topcoder.method_name(data) %>\
class ${topcoder.class_name(data)} {
public:
${cplusplus.return_type(data)} ${topcoder.method_name(data)}(${cplusplus.formal_arguments(data)}) {
// TODO: edit here
}
};
% endif
int main() {
return 0;
}
テンプレートの例(入出力自動化有り)
<%!
import os
import platform
from logging import getLogger
import onlinejudge_template.generator.cplusplus as cplusplus
import onlinejudge_template.generator.topcoder as topcoder
import onlinejudge_template.generator.hook as hook
%>\
<%
logger = getLogger(__name__)
data["config"]["indent"] = "\t"
data["config"]["scanner"] = "cin"
data["config"]["printer"] = "cout"
if platform.system() == "Linux" and "clang" not in os.environ.get("CXX", "g++"):
include = "#include <bits/stdc++.h>"
else:
include = "\n".join([
"#include <iostream>",
"#include <string>",
"#include <vector>",
])
hook.register_filter_command(["sed", "-e", "s/std:://g", "-e", r"s/'\\n'/endl/g"], data=data)
%>\
${include}
using namespace std;
% if topcoder.is_topcoder(data):
<% solve_function = topcoder.class_name(data) + "()." + topcoder.method_name(data) %>\
class ${topcoder.class_name(data)} {
public:
${cplusplus.return_type(data)} ${topcoder.method_name(data)}(${cplusplus.formal_arguments(data)}) {
// TODO: edit here
}
};
% endif
int main() {
${cplusplus.read_input(data)}
${cplusplus.return_type(data)} ${cplusplus.return_value(data)};
${cplusplus.write_output(data)}
return 0;
}
タスクの登録
.vscode
ディレクトリに tasks.json
を作成し、以下のように記述して保存します。
自身の環境に合わせてコマンドや引数の部分を書き換えた方が良いです。
特にコンパイルオプションの部分はかなり好みが出ると思うので、見直すと良い(競プロにおいて pedantic-errors
は特にいらない)と思います。
{
"version": "2.0.0",
"tasks": [
{
"type": "cppbuild",
"label": "build",
"command": "/usr/bin/g++",
"args": [
"-std=c++17",
"-O2",
"-pedantic-errors",
"-Wall",
"-D_GLIBCXX_DEBUG",
"-g",
"${file}",
"-o",
"${fileDirname}/a.out"
],
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "compiler: /usr/bin/g++",
"presentation": {
"focus": true
}
},
{
"type": "shell",
"label": "download test cases",
"command": "${workspaceFolder}/configs/dltest.sh",
"args": [
"${relativeFileDirname}"
]
},
{
"type": "shell",
"label": "do oj-prepare",
"command": "pipenv",
"args": [
"run",
"oj-prepare",
"--config-file",
"configs/prepare.config.toml",
"${input:service_url}/${input:contest_id}"
],
"problemMatcher": []
},
{
"type": "shell",
"label": "do oj test",
"command": "pipenv",
"args": [
"run",
"oj",
"t",
"-c",
"${fileDirname}/a.out",
"-d",
"${fileDirname}/test"
],
"dependsOn": [
"build",
"download test cases"
],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"focus": true
}
},
{
"type": "shell",
"label": "submit",
"command": "${workspaceFolder}/configs/submit.sh",
"args": [
"${relativeFile}"
]
}
],
"inputs": [
{
"id": "service_url",
"type": "pickString",
"description": "Contest site URL",
"options": [
"https://atcoder.jp/contests",
"https://codeforces.com/contest"
],
"default": "https://atcoder.jp/contests"
},
{
"id": "contest_id",
"type": "promptString",
"description": "Contest ID"
}
]
}
Ctrl
+Shift
+P
を押して Tasks: Run Task
を選択すると、登録したタスクが出てきて、選択するだけで実行できるようになっています。
一応、使用するタスクを説明しておくと、
-
do oj-prepare
: コンテストのディレクトリ作成とテストケースのダウンロード -
do oj test
: 入力例などによるテスト -
submit
: コードの提出
となっています。
他のタスクは直接使用することはないと思います。
始まっていないコンテストで do oj-prepare
はできないので注意してください。
タスクをショートカット一発で実行できるようにする場合は keybindings.json
に適当に追記すればよいです。
あとは実際に問題を解いて提出してみてください。
オプション: 競プロ用ライブラリの include
競プロ用のライブラリを持っており、使う度にコピペをしているという場合は online-judge-tools/verification-helper の oj-bundle を導入するとコーディング体験がかなり良くなると思います。
これを利用すると include guard を考慮した上で include を自動で展開してくれます。
競プロ用ライブラリのコピー
競プロ用のライブラリを git 等で管理している場合は clone をします。
もし、このコンテスト用のディレクトリを git で管理しようと思っているのであれば submodule の方がよいかもしれません。
git 等で管理していないのであれば、適当にリンクを貼ってください。
ここでは競プロ用ライブラリのディレクトリ名を library
と置いて説明していきます。
online-judge-tools/verification-helper のインストール
下記のコマンドでインストールします。
pipenv install online-judge-verify-helper
シェルスクリプトの作成と修正
configs
ディレクトリの中に bundle.sh
という名前でファイルを作成し、以下のように記述して、パーミッションを変更します。
oj-bundle
を使うと #line ...
のような表示や、余分な空行がでてくるので、それらを消す処理も入れています。
#!/bin/bash
file_dir_name=$1
file_basename_no_extension=$2
file_extname=$3
file=${file_dir_name}/${file_basename_no_extension}${file_extname}
bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}
pipenv run oj-bundle -I library ${file} \
| grep -v '^#line ' \
| cat -s \
| sed -e '1{/^$/d}' \
| clang-format --style=file \
> ${bundled_file}
configs/submit.sh
がバンドル前のファイルを提出するようになっているので、バンドル後のファイルを提出するように修正します。
@@ -1,10 +1,15 @@
#!/bin/bash
-submit_file=$1 # relative file path (${service_name}/${contest_id}/${problem_id}/main.cpp)
+file_dir_name=$1
+file_basename_no_extension=$2
+file_extname=$3
-service_name=${submit_file%%/*}
-contest_id=$(basename ${submit_file%/*/*})
-problem_id=$(basename ${submit_file%/*})
+file=${file_dir_name}/${file_basename_no_extension}${file_extname}
+bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}
+
+service_name=${file%%/*}
+contest_id=$(basename ${file%/*/*})
+problem_id=$(basename ${file%/*})
case "$service_name" in
"AtCoder" )
@@ -15,4 +20,4 @@ case "$service_name" in
;;
esac
-pipenv run oj s -y ${problem_url} ${submit_file}
+pipenv run oj s -y ${problem_url} ${bundled_file}
修正後のファイル
#!/bin/bash
file_dir_name=$1
file_basename_no_extension=$2
file_extname=$3
file=${file_dir_name}/${file_basename_no_extension}${file_extname}
bundled_file=${file_dir_name}/${file_basename_no_extension}_bundled${file_extname}
service_name=${file%%/*}
contest_id=$(basename ${file%/*/*})
problem_id=$(basename ${file%/*})
case "$service_name" in
"AtCoder" )
problem_url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
;;
"Codeforces" )
problem_url="https://codeforces.com/contest/${contest_id}/problem/${problem_id}"
;;
esac
pipenv run oj s -y ${problem_url} ${bundled_file}
タスクの修正
tasks.json
を修正して、バンドルしたファイルを提出するようにします。
@@ -12,6 +12,8 @@
"-Wall",
"-D_GLIBCXX_DEBUG",
"-g",
+ "-I",
+ "./library",
"${file}",
"-o",
"${fileDirname}/a.out"
@@ -79,7 +81,22 @@
"label": "submit",
"command": "${workspaceFolder}/configs/submit.sh",
"args": [
- "${relativeFile}"
+ "${relativeFileDirname}",
+ "${fileBasenameNoExtension}",
+ "${fileExtname}"
+ ],
+ "dependsOn": [
+ "do oj-bundle"
+ ],
+ },
+ {
+ "type": "shell",
+ "label": "do oj-bundle",
+ "command": "${workspaceFolder}/configs/bundle.sh",
+ "args": [
+ "${relativeFileDirname}",
+ "${fileBasenameNoExtension}",
+ "${fileExtname}"
]
}
],
修正後のファイル
{
"version": "2.0.0",
"tasks": [
{
"type": "cppbuild",
"label": "build",
"command": "/usr/bin/g++",
"args": [
"-std=c++17",
"-O2",
"-pedantic-errors",
"-Wall",
"-D_GLIBCXX_DEBUG",
"-g",
"-I",
"./library",
"${file}",
"-o",
"${fileDirname}/a.out"
],
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "compiler: /usr/bin/g++",
"presentation": {
"focus": true
}
},
{
"type": "shell",
"label": "download test cases",
"command": "${workspaceFolder}/configs/dltest.sh",
"args": [
"${relativeFileDirname}"
]
},
{
"type": "shell",
"label": "do oj-prepare",
"command": "pipenv",
"args": [
"run",
"oj-prepare",
"--config-file",
"configs/prepare.config.toml",
"${input:service_url}/${input:contest_id}"
],
"problemMatcher": []
},
{
"type": "shell",
"label": "do oj test",
"command": "pipenv",
"args": [
"run",
"oj",
"t",
"-c",
"${fileDirname}/a.out",
"-d",
"${fileDirname}/test"
],
"dependsOn": [
"build",
"download test cases"
],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"focus": true
}
},
{
"type": "shell",
"label": "submit",
"command": "${workspaceFolder}/configs/submit.sh",
"args": [
"${relativeFileDirname}",
"${fileBasenameNoExtension}",
"${fileExtname}"
],
"dependsOn": [
"do oj-bundle"
],
},
{
"type": "shell",
"label": "do oj-bundle",
"command": "${workspaceFolder}/configs/bundle.sh",
"args": [
"${relativeFileDirname}",
"${fileBasenameNoExtension}",
"${fileExtname}"
]
}
],
"inputs": [
{
"id": "service_url",
"type": "pickString",
"description": "Contest site URL",
"options": [
"https://atcoder.jp/contests",
"https://codeforces.com/contest"
],
"default": "https://atcoder.jp/contests"
},
{
"id": "contest_id",
"type": "promptString",
"description": "Contest ID"
}
]
}
これで提出時にバンドルされたファイルが提出されるようになりました。
Discussion