LoginSignup
98
55

More than 3 years have passed since last update.

Rustで競技プログラミングをするときの"スニペット管理"をまじめに考える(cargo-snippetの紹介)

Last updated at Posted at 2018-06-15

はじめに

競技プログラミングでは基本的にmain関数を含む1ファイルを提出することになるので外部クレートを使用することができません。

なので必然的に標準ライブラリにない機能を使用したいとき等はスニペット(コード片)をどこかからコピペしてプログラムを書くことになります。

ではそのスニペットたちはどのように管理すればよいでしょうか?

そのことについて考えた結果cargo-snippetというツールを開発するに至ったので紹介したいと思います。

Rustで(本格的なスニペット管理を必要とするほど)競技プログラミングをやっている方はあまりいないかもしれませんが参考になる方がいれば幸いです:bow:

背景

VimのプラグインUltisnipsによるスニペット操作の様子

こんなかんじで、大抵のエディタにはあらかじめ登録しておいたスニペットを入力する機能があります。

しかし、エディタのスニペット機能だけでスニペットを管理しようとすると直接編集するのが面倒な上にテストが書けないという問題点があります。例えばVScodeでのスニペットの形式はこんな感じ
ならばエディタのスニペット機能を使わないでクレート上にコード+テストを書いて、必要なときにコピペするというやり方も考えられますが、面倒です

ということで、Rustのアノテーション機能を使うことで「クレートからスニペットのみを抜き出す」ことでこれらを解決するツールがcargo-snippetです。

インストール

rustfmtが必要なのでインストール
$ rustup component add rustfmt

$ cargo install cargo-snippet --features="binaries"

使い方

まずスニペットのためのクレートを作成します。
このクレートにスニペットを書いていきます。

$ cargo new mysnippet

Cargo.tomldependenciescargo-snippetを追加します

Cargo.toml
[dependencies]
cargo-snippet = "0.4"

試しにスニペットとテストを書いてみます

src/lib.rs
use cargo_snippet::snippet;

// こんなかんじでアノテーションで以下の関数がスニペットであることを指定します
// この場合mymathとgcdという名前のスニペットであることを表しています
#[snippet("mymath")]
#[snippet("gcd")]
fn gcd(a: u64, b: u64) -> u64 {
    if b == 0 {
        a
    } else {
        gcd(b, a % b)
    }
}

// こっちの書き方でもいいです
#[snippet(name = "mymath")]
// 名前を省略すると関数名がそのままスニペット名になります
#[snippet]
fn lcm(a: u64, b: u64) -> u64 {
    a / gcd(a, b) * b
}

#[snippet]
// スニペットに依存関係がある場合は以下のように指定できます
#[snippet(include = "gcd")]
fn gcd_list(list: &[u64]) -> u64 {
    list.iter().fold(list[0], |a, b| gcd(a, b));
}

#[test]
fn test_gcd() {
    assert_eq!(gcd(57, 3), 3);
}

#[test]
fn test_lcm() {
    assert_eq!(lcm(3, 19), 57);
}

もちろんテストできます!

$ cargo test

最後に、cargo snippetコマンドでスニペットを抽出できます!
デフォルトではVimのプラグインであるNeosnippetの形式で出力します。

$ cargo snippet
snippet gcd
    fn gcd(a: u64, b: u64) -> u64 {
        if b == 0 {
            a
        } else {
            gcd(b, a % b)
        }
    }

snippet gcd_list
    fn gcd(a: u64, b: u64) -> u64 {
        if b == 0 {
            a
        } else {
            gcd(b, a % b)
        }
    }
    fn gcd_list(list: &[u64]) -> u64 {
        list.iter().fold(list[0], |a, b| gcd(a, b));
    }

snippet lcm
    fn lcm(a: u64, b: u64) -> u64 {
        a / gcd(a, b) * b
    }

snippet mymath
    fn gcd(a: u64, b: u64) -> u64 {
        if b == 0 {
            a
        } else {
            gcd(b, a % b)
        }
    }
    fn lcm(a: u64, b: u64) -> u64 {
        a / gcd(a, b) * b
    }

現在、以下の形式の出力に対応しています

  • Neosnippet
  • VScode
  • Ultisnips

-tオプションで出力形式を指定できます。
やり方はcargo snippet -hで確認してみてください。

おわりに

個人的に、Rustで競技プログラミングをやるときの大きな欠点は

  • 入出力等のボイラープレートが多い
  • 標準ライブラリが(C++に比べて)貧弱

で、これらはスニペットを使うことでほぼ解決できると思っています:muscle:

自分の観測範囲では他の言語にはこのようなスニペット管理ツールはないのでRust競プロerはcargo-snippetで他の言語に差をつけてはいかがでしょうか:grinning:

ちなみに私のスニペットはhatoo/competitive-rust-snippetsで管理しています。

98
55
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
55