競プロで使えるVSCodeのスニペット集

はじめに

こんにちは、Pike8です。みなさんは競プロでテンプレートやスニペットは使っていますか。Pike8は主にVSCodeのスニペットを使っています。今回はPike8が何度か競プロに参加して更新してきたスニペットを紹介したいと思います。

テンプレート VS スニペット

競プロを始めたとき、テンプレートを使うかスニペットを使うかで悩みました。ここでいうテンプレートとは、以下のようによく使うマクロや定数、関数などを書いたファイルを事前に準備し、問題を解く際にコピーして使うというものです。

#include <bits/stdc++.h>
using namespace std;

// clang-format off
struct Fast {Fast() {std::cin.tie(0); ios::sync_with_stdio(false);}} fast;
// clang-format on

#define REP(i, n) for (int i = 0; i < (int)(n); i++)

// Add more macro, constants or function here

int main() {
    
}

一方スニペットは、VSCodeでは .vscode/.[任意の文字列].code-snippets に以下のようなJSONを追加し、エディタの補完機能でコードを補完していきます。IntelliJやCLionを使う場合は、ライブテンプレートという同様の機能を利用します。

{
    "main": {
        "body": [
            "#include <bits/stdc++.h>",
            "using namespace std;",
            "",
            "// clang-format off",
            "struct Fast {Fast() {std::cin.tie(0); ios::sync_with_stdio(false);}} fast;",
            "// clang-format on",
            "#define int long long",
            "",
            "int main() {",
            "    $0",
            "}",
            ""
        ],
        "prefix": "main"
    },
    "for": {
        "body": [
            "for (int ${1:i} = ${2:0}; $1 < ${3:count}; $1++) {",
            "    $0",
            "}"
        ],
        "prefix": "for"
    },
    // Add more snippets here
}

テンプレートを使うメリットとして、環境を選ばないことがあります。個人のPCやエディタを使えないコンテストにも有効です。一方でスニペットを使うメリットは、ファイルから使わないコードを省くことができ、その結果、かなり気軽に新たなスニペットを追加できます。またマクロを使わなくてよいため、コードを読む際の可読性も高いです。Pike8はAtCoderをメインとして競プロに参加しており、前者にあまりメリットを感じなかったため、現在はテンプレートではなくスニペットを利用しています。

スニペット

Pike8がAtCoderで使用している.code-snippetsファイルはこちらになります。以下にこのファイルで定義されているスニペットを紹介します。

コード中に全て大文字で書かれている単語は、スニペットにより補完したときに順次カーソルがフォーカスし、容易に変更することができます。

main関数

// "main"
#include <bits/stdc++.h>
using namespace std;

// clang-format off
struct Fast {Fast() {std::cin.tie(0); ios::sync_with_stdio(false);}} fast;
// clang-format on
#define int long long

signed main() {
    
}

main関数と毎回追加するinclude文などを補完します。また、Pike8は intlong long として扱うようにしています。これには賛否両論あり、Pike8も長く使わないようにしていたのですが、忘れたころに起こる int のオーバーフローによるWAを避けるために今は取り入れています。ただしこの場合でも、atoll を使うべきところで atoi を使ったり、accumulate の初期値に 0LL を渡すべきところで 0 を渡したりするとオーバーフローが起きるので注意が必要です。

入力

// "ci"
TYPE IN;
cin >> IN;

// "ci2" ~ "ci5"
TYPE IN1, IN2;
cin >> IN1 >> IN2;

// "civec"
vector<TYPE> VEC(SIZE);
for (auto &&in : VEC) {
    cin >> in;
}

// "civec2"
vector<vector<TYPE>> VEC(SIZE1, vector<TYPE>(SIZE2));
for (auto &&row : VEC) {
    for (auto &&in : row) {
        cin >> in;
    }
}

civeccivec2 は標準入力を vector に読み込む際に使います。

出力

// "co"
cout << OUT << '\n';

// "co2" ~ "co5"
cout << OUT1 << OUT2 << '\n';

// "covec"
for (auto out = VEC.begin(); out != VEC.end(); out++) {
    cout << *OUT << (out != --VEC.end() ? ' ' : '\n');
}

// "covec2"
for (auto &&row : VEC) {
    for (auto out = row.begin(); out != row.end(); out++) {
        cout << *OUT << (out != --row.end() ? ' ' : '\n');
    }
}

// "coyesno"
cout << (CONDITION ? "Yes" : "No") << '\n';

// "coaccuracy"
cout << fixed << setprecision(12);

// "cobool"
cout << boolalpha;

// "copad"
cout << setfill('0') << setw(LENGTH) << OUT << '\n';

coveccovec2vector を書き出す際に使います。coyesno は条件により YesNo を出力します。補完時に実際に出力する文字の形式を選ぶことができます。

タイプ

// "ll"
long long

// "vec"
vector<TYPE>

// "vec2", "vec3"
vector<vector<TYPE>>

// "set", "umap"
set<TYPE>

// "uset", "umap"
unordered_set<TYPE>

// "usetcustom", "umapcustom"
unordered_set<TYPE, TYPE::Hash>

// "pq"
priority_queue<TYPE>

// "pqreverse"
priority_queue<TYPE, vector<TYPE>, greater<TYPE>>

多次元 vectorunordered_set など、長いタイプを補完します。usetcustomumapcustom は、自分で定義した構造体を unordered_setunordered_map の要素にするときに使います。Hash などを定義した構造体はこちらのスニペットで補完できます。

変数宣言

// "vecsized"
vector<TYPE> NAME(SIZE);

// "vec2sized", "vec3sized"
vector<vector<TYPE>> NAME(SIZE1, vector<TYPE>(SIZE2));

// "pqlambda"
priority_queue<TYPE, vector<TYPE>, function<bool(TYPE, TYPE)>> NAME([&](TYPE VALUE1, TYPE VALUE2) {  });

vecsized はサイズを指定した vector の宣言です。多次元 vector になると TYPE の指定が重複するので一度に入力できると便利です。pqlambda は、比較可能ではない構造体を TYPE に指定し、比較方法をラムダで指定するときに利用します。

構造体

// "struct2", "struct3"
template <class Type> inline void hash_combine(size_t &h, const Type &value) noexcept {
    size_t k = hash<Type>{}(value);
    uint64_t m = 0xc6a4a7935bd1e995;
    int r = 47;
    k *= m;
    k ^= k >> r;
    k *= m;
    h ^= k;
    h *= m;
    h += 0xe6546b64;
}

struct NAME {
    TYPE1 VALUE1;
    TYPE2 VALUE2;

    string to_string() const {
        ostringstream oss;
        oss << "NAME(VALUE1=" << VALUE1 << ", VALUE2=" << VALUE2 << ")";
        return oss.str();
    }

    bool operator==(const NAME &other) const {
        return tie(VALUE1, VALUE2) == tie(other.VALUE1, other.VALUE2);
    }

    bool operator<(const NAME &other) const {
        return make_pair(VALUE1, VALUE2) < make_pair(other.VALUE1, other.VALUE2);
    }

    struct Hash {
        size_t operator()(const NAME &p) const noexcept {
            size_t seed = 0;
            hash_combine(seed, p.VALUE1);
            hash_combine(seed, p.VALUE2);
            return seed;
        }
    };
};

要素を2つ、ないし3つ持つ構造体のためのスニペットです。setmap で利用するために operator< をオーバーロードしています。また、unordered_setunordered_map で利用するために Hash 構造体を定義しています。pairtuple では要素のアクセスにミスが起こりやすいので、Pike8はできるだけこちらのスニペットで構造体を定義しています。

定数

// "infi"
const int INF = 1e9;

// "infll"
const long long INF = 1e18;

// "dxdy"
const vector<int> DX = {1, 0, -1, 0};
const vector<int> DY = {0, 1, 0, -1};

// "drowdcol"
const vector<int> DROW = {1, 0, -1, 0};
const vector<int> DCOL = {0, 1, 0, -1};

dxdydrowdcol は座標平面や二次元 vector の前後左右を走査する際に利用しています。

ループ

// "for"
for (int I = 0; I < COUNT; I++) {
    
}

// "forreverse"
for (int I = COUNT - 1; I >= 0; I--) {
    
}

// "forrange"
for (auto &&ELEMENT : VEC) {
    
}

通常のfor文、デクリメントするfor文、範囲for文を書くためのスニペットです。

頻度が高いコード

// "lf"
'\n'

// "lforspace"
(CONDITION ? ' ' : '\n')

// "all", "allreverse"
VEC.begin(), VEC.end()

// "chmax", "chmin"
MAX = max(MAX, TARGET);

// "rangeexclusive", "rangeinclusive"
MIN <= VALUE && VALUE < MAX

lforspacevector を出力する際などにたまに利用します。allallreversesortaccumulate など vector を引数として渡すときに利用します。chmaxchmin は計算結果が現在の値よりも大きかったり、小さかったりしたときに限り現在の値を更新する場合に利用します。rangeexclusiverangeinclusiveは二次元配列の走査などでインデックスが配列の範囲内に収まっているかをチェックするときなどに利用します。

間違いやすいコード

// "popcount"
__builtin_popcount(VALUE)

__builtin_popcount は数値のビットが1になっている個数を返します。bit というヘッダファイルに似た名前で __popcount という関数が定義されています。一度、bit をインクルードせずに __popcount を使ってWAになって以降、こちらのスニペットを利用しています。

関数とラムダ

// "fun"
function<RET(TYPE)> NAME = [&](TYPE VALUE) {
    
};

// "fun2"
function<RET(TYPE1, TYPE2)> NAME = [&](TYPE1 VALUE1, TYPE2 VALUE2) {
    
};

// "lambda"
[&](AUTO const &VALUE) {  }

// "lambda2"
[&](AUTO1 const &VALUE1, AUTO2 const &VALUE2) {  }

funfun2 は関数の中でローカル関数を定義したいときに利用します。DFSなど再帰関数を定義するときに便利です。lambdalamda2sortaccumulate などに渡したいときに利用します。すべてクロージャに & を指定しているので、どのスコープ内の変数にもアクセスできます。競プロで書くコードならスコープを気にしなくてもそこまで問題はないと思います。

コレクション操作

// "filter"
vector<TYPE> FILTERED;
copy_if(VEC.begin(), VEC.end(), back_inserter(FILTERED), [&](TYPE const &value) {  });

// "transform"
vector<TYPE> TRANSFORMED;
transform(VEC.begin(), VEC.end(), back_inserter(TRANSFORMED), [&](AUTO const &value) {  });

// "partialsum"
vector<TYPE> SUMS = {0};
partial_sum(VEC.begin(), VEC.end(), back_insert_iterator(SUMS));

よく使うコレクションに対する操作です。transform はいわゆるmap処理です。partialsum は累積和を求めるのに利用します。

アルゴリズム

二分探索

// "binarysearch"
auto isOk = [&](TYPE mid) {
    
};
TYPE ok = OKBOUND;
TYPE ng = NGBOUND;
while (abs(ok - ng) > 1) {
    type mid = (ok + ng) / 2;
    if (isOk(mid)) {
        ok = mid;
    } else {
        ng = mid;
    }
}

いわゆるめぐる式二分探索を補完します。

文字列変換

// itos
string itos(long long value, int radix) {
    if (value == 0) {
        return "0";
    }
    string res;
    bool isNegative = value < 0;
    value = abs(value);
    while (value > 0) {
        int r = value % radix;
        char c = r <= 9 ? r + '0' : r - 10 + 'a';
        res = c + res;
        value /= radix;
    }
    if (isNegative) {
        res = '-' + res;
    }
    return res;
}

数値を文字列に変換する関数を補完します。std::to_string では基数を指定できないので、基数の指定が必要なときに利用します。

繰り返し二乗法

// "powll"
long long powll(long long base, long long exponent) {
    long long ret = 1;
    long long remain = exponent;
    while (remain > 0) {
        if (remain % 2 == 1) {
            ret *= base;
        }
        base *= base;
        remain /= 2;
    }
    return ret;
}

繰り返し二乗法で累乗を計算する関数を補完します。競プロでは累乗をある数で割った余りを求めることも多いのですが、こちらの関数では剰余は行っていません。剰余を求める場合は、AtCoderでは Modintpow 関数を使うとよいでしょう。

素数

// "prime"
bool is_prime(long long n) {
    if (n == 1) {
        return false;
    }
    for (long long i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

// "primefact"
vector<long long> get_prime_factorization(long long n) {
    vector<long long> ret;
    long long remain = n;
    for (long long i = 2; i * i <= remain; i++) {
        while (remain % i == 0) {
            ret.push_back(i);
            remain /= i;
        }
    }
    if (remain != 1) {
        ret.push_back(remain);
    }
    return ret;
}

prime は素数を判定するための関数を補完します。primefact は素因数分解を行う関数を補完します。

順列、組み合わせ

// "permutation"
void foreach_permutation(vector<TYPE> list, function<void(vector<TYPE>)> callback) {
    vector<TYPE> permutation(list.size());
    vector<int> indexes(list.size());
    iota(indexes.begin(), indexes.end(), 0);
    do {
        for (int i = 0; i < indexes.size(); i++) {
            permutation[i] = list[indexes[i]];
        }
        callback(permutation);
    } while (next_permutation(indexes.begin(), indexes.end()));
}

// "permutation2"
void foreach_permutation(vector<TYPE> list, int count, function<void(vector<TYPE>)> callback) {
    vector<TYPE> permutation(count);
    vector<TYPE> combination(count);
    vector<int> indexes(count);
    iota(indexes.begin(), indexes.end(), 0);
    function<void(int, int)> dfs = [&](int position, int index) {
        if (position == count) {
            do {
                for (int i = 0; i < indexes.size(); i++) {
                    permutation[i] = combination[indexes[i]];
                }
                callback(permutation);
            } while (next_permutation(indexes.begin(), indexes.end()));
            return;
        }
        for (int i = index; i < list.size(); i++) {
            combination[position] = list[i];
            dfs(position + 1, i + 1);
        }
    };
    dfs(0, 0);
}

// "combination"
void foreach_combination(vector<type> list, int count, function<void(vector<type>)> callback) {
    vector<type> combination(count);
    function<void(int, int)> dfs = [&](int position, int index) {
        if (position == count) {
            callback(combination);
            return;
        }
        for (int i = index; i < list.size(); i++) {
            combination[position] = list[i];
            dfs(position + 1, i + 1);
        }
    };
    dfs(0, 0);
}

permutation は渡した list パラメータの順列に対して、ラムダ式で処理するための関数を補完します。permutation2 はそれに加えて、取り出す要素の個数を指定できるようにしています。combination は 渡した list パラメータから count 個取り出した組み合わせをラムダ式で処理するための関数を補完します。それぞれ、\(_nP_n\)、\(_nP_k\)、\(_nC_k \)に対応します。

AtCoder Library

// "acl"
#include <atcoder/FENWICKTREE>
using namespace atcoder;

// "mint"
using mint = modint;

AtCoder Library用のスニペットです。acl はヘッダのインクルードに利用します。atcoder/all をインクルードするとコンパイル時間に影響しそうだったので、補完時にどのヘッダをインクルードするか選べるようにしています。

mint はAtCoder Libraryの中でもよく使う modintatcoder::modint1000000007atcoder::modint998244353 が長いので、それらを別名 mint で定義するために利用します。

所感

まとめてみるとかなり多くなっていました。C++はコレクションに対する操作など、どうしてもほかの言語と比べてコードが冗長になることが多いですが、テンプレートやスニペットを使ってコーディングの時短をしていきたいです。そして.code-snippetsはこれからもどんどん更新していきたいです。