Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
45
Help us understand the problem. What is going on with this article?

More than 1 year has passed since last update.

@hatoo@github

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

はじめに

競技プログラミングでは基本的に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で管理しています。

45
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
45
Help us understand the problem. What is going on with this article?