みなさんスニペットは好きですか?僕は好きです
ところで私のスニペットを公開したので見てください。(記事: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};