- 元ネタ:ズンドコキヨシ with python / ruby / Lua
- さまざまなズンドコキヨシの一覧
- 2016年3月13日更新:短いバージョンを追加しました。
短いバージョン
みなさんの他言語での実装を参考に、短くしたバージョン。
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
値を作ってくれるところが良い。
オリジナルバージョン
最初に書いたプログラム(に、いくつかの修正を加えたもの)。
短いバージョンとの違い:
- Rust で複雑なプログラムを作成するときに基礎となる言語機能を使っているので勉強になる(というか勉強になった)。トレイト(オブジェクト指向のポリモーフィズムを実現)、列挙型、構造体などを使用
- 任意の終了条件を指定できる。例:
let goal = [Zun, Zun, Zun, Doko, Zun, Doko, Doko]
- 新しい音を簡単に足せる。例:「ドン」
- マルチスレッドで複数のズンドコを実行する
オリジナルバージョンの実行例
% 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
トレイトが必須。Clone
とCopy
を自動導出した
- 文字列
-
構造体:
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 ==
の所がコンパイルできず。if
はzd
から値を借りる形で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 が使えず断念