LoginSignup
9
12

More than 5 years have passed since last update.

ズンドコキヨシ with Rust

Last updated at Posted at 2016-03-12

短いバージョン

みなさんの他言語での実装を参考に、短くしたバージョン。

main.rs
extern crate rand;
use rand::Rng;

fn main() {
    let mut zcnt = 0;
    let mut rng = rand::thread_rng();
    loop {
        // 型推論でbool値を生成(true → ズン、false → ドコ)
        let zd = rng.gen();
        print!("{} ", if zd {"ズン"} else {"ドコ"});
        if zd {
            zcnt += 1
        } else if zcnt >= 4 {
            break
        } else {
            zcnt = 0
        }
    }
    println!(" キ・ヨ・シ!");
}

Rust らしさはほとんどないが、rng.gen() に型推論が活かされていて、自動的に bool 値を作ってくれるところが良い。

オリジナルバージョン

最初に書いたプログラム(に、いくつかの修正を加えたもの)。

短いバージョンとの違い:

  1. Rust で複雑なプログラムを作成するときに基礎となる言語機能を使っているので勉強になる(というか勉強になった)。トレイト(オブジェクト指向のポリモーフィズムを実現)、列挙型、構造体などを使用
  2. 任意の終了条件を指定できる。例:let goal = [Zun, Zun, Zun, Doko, Zun, Doko, Doko]
  3. 新しい音を簡単に足せる。例:「ドン」
  4. マルチスレッドで複数のズンドコを実行する

オリジナルバージョンの実行例

% time ./target/release/zd

8人同時にいくよ! せーのっ

ドコ ズン ズン ドコ ズン ドコ ドコ ズン ズン ズン ドコ ズン ドコ ズン ズン ズン ドコ ズン 
ズン ズン ズン ドコ キ・ヨ・シ! (22回)

ズン ドコ ズン ズン ズン ドコ ズン ドコ ドコ ズン ズン ズン ドコ ドコ ズン ズン ズン ズン 
ズン ズン ドコ キ・ヨ・シ! (21回)

ズン ドコ ドコ ズン ズン ドコ ドコ ズン ドコ ズン ズン ドコ ズン ズン ドコ ズン ズン ズン 
ズン ドコ キ・ヨ・シ! (20回)

ズン ズン ズン ドコ ズン ズン ドコ ズン ドコ ズン ドコ ズン ズン ドコ ドコ ドコ ズン ズン 
ドコ ドコ ズン ドコ ドコ ズン ズン ドコ ドコ ドコ ドコ ズン ドコ ドコ ドコ ドコ ズン ズン 
ズン ズン ズン ズン ズン ズン ドコ キ・ヨ・シ! (43回)

ドコ ズン ドコ ドコ ドコ ドコ ズン ズン ドコ ドコ ドコ ズン ドコ ズン ドコ ドコ ドコ ドコ 
ドコ ドコ ドコ ドコ ドコ ズン ドコ ドコ ドコ ドコ ドコ ズン ドコ ズン ドコ ドコ ドコ ドコ 
ズン ドコ ズン ズン ズン ズン ドコ キ・ヨ・シ! (43回)

ドコ ズン ズン ズン ズン ズン ズン ズン ズン ズン ドコ キ・ヨ・シ! (11回)

ドコ ズン ドコ ズン ドコ ドコ ズン ドコ ドコ ドコ ズン ズン ドコ ズン ドコ ズン ズン ドコ 
ズン ドコ ドコ ドコ ドコ ズン ズン ドコ ドコ ドコ ドコ ドコ ドコ ドコ ドコ ドコ ドコ ズン 
ドコ ドコ ズン ズン ドコ ドコ ズン ドコ ドコ ドコ ドコ ドコ ズン ズン ドコ ズン ズン ドコ 
ドコ ズン ズン ズン ドコ ズン ドコ ドコ ドコ ズン ズン ズン ズン ドコ キ・ヨ・シ! (68回)

ドコ ドコ ズン ズン ズン ドコ ドコ ズン ドコ ズン ズン ドコ ドコ ズン ドコ ドコ ドコ ドコ 
ズン ズン ズン ズン ドコ キ・ヨ・シ! (23回)

./target/release/zd  0.00s user 0.01s system 146% cpu 0.004 total

全員、バラバラですね...

Rust 1.7.0 安定版を使用

% rustc --version --verbose
rustc 1.7.0
binary: rustc
commit-hash: unknown
commit-date: unknown
host: x86_64-unknown-freebsd
release: 1.7.0

オリジナルバージョンのプログラム

最初に書いたもの、にいくつかの修正を加えたもの。

Cargo.toml
[dependencies]
rand = "0.3.14"
src/main.rs
extern crate rand;

use std::fmt;

#[derive(Clone, Copy, PartialEq)]
enum Sound {
    Zun,
    Doko,
}

impl rand::Rand for Sound {
    fn rand<R: Rng>(rng: &mut R) -> Self {
        // 型推論により `bool` 値が生成される
        if rng.gen() {
            Zun
        } else {
            Doko
        }
    }
}

impl fmt::Display for Sound {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let str = match *self {
            Zun => "ズン",
            Doko => "ドコ",
        };
        write!(f, "{}", str)
    }
}

use Sound::*;

struct ZunDoko(Vec<Sound>);

impl ZunDoko {
    // @woxtuさんの簡潔なバージョン(std::slice::ends_withを使用)
    fn play(goal: &[Sound]) -> Self {
        let mut rng = rand::thread_rng();
        let mut zd = Vec::new();

        while !zd.ends_with(goal) {
            // 型推論により rng.gen() で Sound値が生成される。
            zd.push(rng.gen());
        }

        ZunDoko(zd)
    }

    fn print(&self) {
        for item in &self.0 {
            print!("{} ", item);
        }
        println!("キ・ヨ・シ! ({}回)\n", self.0.len());
    }
}

fn main() {
    let num_threads = 8;
    println!("\n{}人同時にいくよ! せーのっ\n", num_threads);

    let goal = [Zun, Zun, Zun, Zun, Doko];

    let handles: Vec<_> = (0..num_threads).map(|_| {
        std::thread::spawn(move || {
            ZunDoko::play(&goal.clone())
        })
    }).collect();

    for h in handles {
        let _ = h.join().map(|zundoko| zundoko.print());
    }
}
  • 列挙型:Sound
    • 文字列 "ズン" "ドコ" を直接比較するのは実行コストがかかり過ぎるので(笑)、列挙型 Sound を作った
    • PartialEq を自動導出すると、スライス &[Sound]== で比較できるようになる。便利
    • rand::sample() は結果を Vec<&Sound> というように借用で返すので、[0].clone() として値の所有権を得た。そのために Clone を自動導出した
    • rand::Rand トレイトを実装し、(rand::sample(長ーい引数) の代わりに)rand::gen() を使えるように変更した。なにより無駄に [Zun, Doko].iter() を生成しなくてよくなったのが嬉しい
    • 表示のために ToString トレイトを実装した
    • 表示には fmt::Display トレイトを実装するように変更した
    • std::thread::spawn(move || { ZunDoko::play(&goal.clone()) })clone()Copy トレイトが必須。CloneCopy を自動導出した
  • 構造体:ZunDoko(Vec<Sound>)
    • ZunDoko::play(&goal)zundoko.print() のように使いたかったので、impl ZunDoko {...} でメソッドを生やした
    • 終了チェックは、最初 let sound = Sound::pick(&mut rng); zd.push(sound); if sound == Doko && ... { ... } のように書いたが、zd.push(sound)Sound の所有権が zd に移るので if sound == の所がコンパイルできず。ifzd から値を借りる形で if let Some(&Doko) = zd.last() と書くのが正解らしい
    • 終了チェックは @woxtu さんが教えてくれた、スライスの ends_with() を使った、簡潔なコードに変更した

GC が無くても、できあがったプログラムは結構自然な感じになりますね。ただ、完成するまでの間 borrow checker 関連でコンパイルエラーが出まくるので、コーディング中に自動コンパイルしてエラーを表示してくれる IDE 的なものがあると、時間が大いに節約できます。私は Emacs + flycheck-rust を使ってます。これがないと辛いかも。

変更前のコード:ToString トレイトを実装していた

  • ToString トレイトだと、プリントの際に to_string() を明示的に呼び出す必要がある。print!("{} ", item.to_string());
  • fmt::Display トレイトなら print!("{} ", item); と書ける。

変更前

src/main.rs
impl ToString for Sound {
    fn to_string(&self) -> String {
        match *self {
            Zun => "ズン".to_owned(),
            Doko => "ドコ".to_owned(),
        }
    }
}

impl ZunDoko {
    fn print(&self) {
        for item in &self.0 {
            print!("{} ", item.to_string());
        }
        println!("キ・ヨ・シ! ({}回)\n", self.0.len());
    }
}

変更前のコード:範囲指定でスライスを作って、== で比較していた

  • std::slice::ends_with() を使って while !zd.ends_with(goal) とした方がずっと簡潔で、意図もはっきりわかる。

変更前

src/main.rs
impl ZunDoko {
    fn play(goal: &[Sound]) -> Self {
        let g_len = goal.len();
        let mut rng = rand::thread_rng();
        let mut zd = Vec::new();

        loop {
            zd.push(Sound::pick(&mut rng));
            if let Some(&Doko) = zd.last() {
                let len = zd.len();
                if len >= g_len && &zd[(len - g_len)..] == goal {
                    break;
                }
            }
        }
        ZunDoko(zd)
    }
}

変更前のコード:rand::sample() を使用していた

main.rs
impl Sound {
    fn pick(mut rng: &mut rand::Rng) -> Self {
        rand::sample(&mut rng, [Zun, Doko].iter(), 1)[0].clone()
    }
}

impl ZunDoko {
    fn play(goal: &[Sound]) -> Self {
        // 一部省略...

        while !zd.ends_with(goal) {
            zd.push(Sound::pick(&mut rng));

追記

  • Rust Playground で実行できるようにしたかったが、外部クレート rand が使えず断念
9
12
3

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
9
12