[厳選版!]競プロ用スニペット (C++)

みなさんスニペットは好きですか?僕は好きです

ところで私のスニペットを公開したので見てください。(記事:https://eoeo-kyopro.hatenablog.com/entry/2022/12/25/225223) そして、この記事を読んだ皆さんのスニペットも見せてください。

この記事では上の記事に大量にあるスニペットの中かいくつか選んで紹介することが目的です。

環境等

私はC++VScodeを使っているのでこの記事でもそれらに固有の話題が出てきます。ただ他の言語やエディタでも似たようなことができるのではないかと思います。

VScodeにおけるスニペット

VScodeにおけるスニペットに関する基本的な情報についてはこの記事が詳しいです。https://qiita.com/12345/items/97ba616d530b4f692c97 さらに、スニペットを扱う際に私は次の拡張機能を用いています。(Snippet GeneratorはMacでは使えないかも?)

Snippet Generator https://marketplace.visualstudio.com/items?itemName=fiore57.snippet-generator (作者さまの紹介ページ https://www.pc-gear.com/post/vscode-snippet-generator/)

Easy Snippet https://marketplace.visualstudio.com/items?itemName=inu1255.easy-snippet

スニペットの可変値

VScodeではスニペット内のある文字列を呼び出した際に書き換えることができます。詳しくは上の記事を参照してください。スニペットの可変値を用いるときに大切なことは複数の可変な箇所を同時に編集できることです。例えば、次のように書くと

"vector of ll": {
    "prefix": "vl",
    "description": "",
    "body": [
      "vector<ll> ${1:a}(${2:n});",
      "for (ll i = 0; i < ${2:n}; i++) {",
      "    cin >> ${1:a}[i];",
      "} "
    ]
  }

vlをタイピングして上のスニペットを呼び出した瞬間にカーソルはスニペットの1行目と3行目にある \${1:a}のそれぞれの位置に移動します。そして、二つの箇所を同時に編集することができます。編集を終えた後は tabキーを押すと\${2:n}の二か所にカーソルが移動します。

なお、\${1:hoge}と書いたときのhogeでデフォルトの値を指定できます。よく使う変数名を指定しておくと良いと思います。

さらに、\$0はカーソルの最終地点となります。デフォルトではカーソルの最終地点は スニペットの最後尾の後ろとなりますが、 次のfor文のスニペットのようにスニペットの途中に最終地点を設定したいときには\$0が便利です。

"for i 0": {
    "prefix": "fi",
    "description": "",
    "body": [
      "for (ll i = 0; i < ${1:n}; i++) {",
      "    $0",
      "}"
    ]
  }

スニペット実践編

以下は先週のABCにおいて私が本番中にACしたコードを少し変えたものです。

int main() {
    string s;
    cin >> s;
 
    vector<vector<char>> box(1);
    set<char> st;
    bool can = true;
 
    for (ll i = 0; i < s.size(); i++) {
        if (s[i] == '(') {
            box.push_back(vector<char>(0));
        }
        else if (s[i] == ')') {
            vector<char> ed = box.back();
            for (auto&& c : ed) {
                if (st.count(c)) {
                    st.erase(c);
                }
            }
            box.pop_back();
        }
        else {
            if (st.count(s[i])) {
                can = false;
            }
            else {
                st.insert(s[i]);
                if (box.size() > 0) box.back().push_back(s[i]);
            }
        }
    }
 
    if (can)
        cout << "Yes" << '\n';
    else
        cout << "No" << '\n';
}

このコードを書く際に用いたスニペットが以下になります。

// 入力
"string cin": {
  "prefix": "st",
  "description": "",
  "body": [
    "string ${1:s};",
    "cin >> ${1:s};"
  ]
},

// 型、変数の宣言
"type vc": {
  "prefix": "vct",
  "body": "vector<char>",
  "description": "型"
},
"type vvc": {
  "prefix": "vvct",
  "body": "vector<vector<char>>",
  "description": "型"
},
"set": {
  "prefix": "set",
  "body": "set<ll> st;",
  "description": "順序付き集合"
},
"can": {
  "prefix": "can",
  "description": "",
  "body": [
    "bool can = ${1:true};"
  ]
},

// 頻出処理
"for i 0": {
  "prefix": "fi",
  "description": "",
  "body": [
    "for (ll i = 0; i < ${1:n}; i++) {",
    "    $0",
    "}"
  ]
},
"for auto": {
  "prefix": "fa",
  "description": "",
  "body": [
    "for (auto&& ${2:x} : ${1:a}) {",
    "\t$0",
    "}"
  ]
},
"YesNo": {
  "prefix": "yn",
  "description": "",
  "body": [
    "if (${1:can})",
    "    cout << \"Yes\" << '\\n';",
    "else",
    "    cout << \"No\" << '\\n';"
  ]
},

これは流石に過剰なスニペットですが頻出するコードに対してスニペットを用意するとコーディング時間の短縮に 繋がると思います。また、変数名もスニペットの中で統一すると便利です。(例えば、可能か否かを表す変数名は can と決めておく。)

ここに挙げたものを含めてスニペットでできることはマクロや関数でもできるかとは思いますが誰が読んでも分かりやすい、また、その場ですぐに 細かい部分を調節できる、という点でスニペットを使うメリットがあると思います。

厳選版!スニペット

本記事の主題ですがもう疲れてきてしまったのでEasy Snippetの形式で個人的に有用なものを選んでペタリで失礼します。

// @prefix l
// @description 

ll ${1:n};
cin >> ${1:n};


// @prefix l2
// @description 

ll ${1:n}, ${2:m};
cin >> ${1:n} >> ${2:m};


// @prefix vl
// @description 

vector<ll> ${1:a}(${2:n});
for (ll i = 0; i < ${2:n}; i++) {
    cin >> ${1:a}[i];
} 


// @prefix vl2
// @description 

vector<ll> ${1:a}(${3:n}), ${2:b}(${3:n});
for (ll i = 0; i < ${3:n}; i++) {
    cin >> ${1:a}[i] >> ${2:b}[i];
}  // Aren't they one line?


// @prefix civv
// @description 

for (ll i = 0; i < ${2:n}; i++) {
    for (ll j = 0; j < ${3:m}; j++) {
        cin >> ${1:a}[i][j];
    }
}


// @prefix ans
// @description 

ll ans = ${1:0};


// @prefix co
// @description 

cout << ${1:ans}$0 << '\n';


// @prefix vvvl
// @description 三次元配列

vector dp(${1:n}, vector(${2:m}, vector<ll>(${3:l})));


// @prefix cov
// @description 

for (auto&& el : ${1:a}) {
    cout << el << ' ';
}
cout << '\n';


// @prefix lm
// @description ラムダ式

auto ${1:check} = [&](${2:ll k}) {
    $0
};



// @prefix dfs
// @description 再帰ラムダ

auto dfs = [&](auto&& self, ll$1) -> void { // self(self, );
    $0
};
// dfs(dfs, );


// @prefix fi
// @description 

for (ll i = 0; i < ${1:n}; i++) {
    $0
}


// @prefix fj
// @description 

for (ll j = 0; j < ${1:m}; j++) {
    $0
}


// @prefix fk
// @description 

for (ll k = 0; k < ${1:n}; k++) {
    $0
}


// @prefix fap
// @description 

for (auto&& [${2:x}, ${3:y}] : ${1:a}) {
 $0
}


// @prefix lb
// @description イテレータ

auto ${3:itr} = lower_bound(${1:a}.begin(), ${1:a}.end(), $0); // set, mapは map.lower_bound(探したい数字)


// @prefix psum
// @description 累積和

vector<ll> ${1:psum}(${2:a}.size());
partial_sum(${2:a}.begin(), ${2:a}.end(), ${1:psum}.begin());


// @prefix per
// @description 順列全探索

vector<ll> per(${1:n});
iota(per.begin(), per.end(), ${2:0}); // 0 based or 1 based
do {
    $0
} while (next_permutation(per.begin(), per.end()));


// @prefix visit4
// @description 上下左右

for (ll i = 0; i < 4; i++) {
    ll X = ${1:x} + dx[i], Y = ${2:y} + dy[i];
    if (X < 0 || h <= X || Y < 0 || w <= Y) continue;
    $0
}


// @prefix xy4
// @description 座標探索

vector<ll> dy(4);
dy = {0, 0, 1, -1};
vector<ll> dx(4);
dx = {1, -1, 0, 0};