LoginSignup
3
1

More than 1 year has passed since last update.

【Rust】チュートリアルが終わったのでFizzBuzzを試しに書いてみた。

Last updated at Posted at 2020-05-18

はじめに

様々な方々の記事を拝見していてRustに興味が湧いた僕。
Rustのチュートリアルを読み進め、ようやく一通り読み終わることができました。
せっかくなので復習がてら何か作成してみよう!ということで、よくある題材かとは思いますがFizzBuzzを作成してみることにします。
最終的な作成物はこちらになります。

FizzBuzzとは

念のため、FizzBuzzについて簡単に説明します。といってもWikipediaから貼るだけですが...( ¯•ω•¯ )

Fizz Buzz

最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」(Bizz Buzzの場合は「Bizz」)、5で割り切れる場合は「Buzz」、両者で割り切れる場合(すなわち15で割り切れる場合)は「Fizz Buzz」(Bizz Buzzの場合は「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

Wikipediaに頼った方が逆に難解なんじゃね...? 端的にいうと、以下な出力がされるイメージです。

  • 3で割り切れる数ならFizz
  • 5で割り切れる数ならBuzz
  • 3と5の両方で割り切れる数ならFizzBuzz
  • 上記以外であれば数字
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Rustで書いてみる

そんなわけでRustでFizzBuzzを書いていきます。
適当なフォルダにプロジェクトを作成します。

cargo new --bin fizz_buzz

まずは普通に書いてみる

とりあえず普通?というかざっくり書いてみます。
せっかくのRustなのでmatch式とガードを使用してみました。

src/main.rs
fn main() {
    for i in 1..101 {
        let answer = match i {
            i if i % 15 == 0 => String::from("FizzBuzz"),
            i if i % 3 == 0 => String::from("Fizz"),
            i if i % 5 == 0 => String::from("Buzz"),
            _ => i.to_string(),
        };
        println!("{}",answer);
    }
}

とりあえずこれで完成ではありますが、少々あっさりして物足りない感が否めない。。。
せっかくなので、これをベースとして更に手を加えてみます。

モジュールを分けてみる

ロジック部分を別モジュールに分けてみます。

src/lib.rs
/// `Manager`は`FizzBuzz`ゲームを管理します。
pub struct Manager {
    fizz_num: u32,
    buzz_num: u32,
    fizz_buzz_num: u32,
    max: u32,
}

impl Manager {
    /// 新しい`Manager`を作成します。引数に応じてルールを設定します。
    ///
    /// # Argments
    ///
    /// * `fizz_num` - 値を`Fizz`とするための基準値
    /// * `buzz_num` - 値を`Buzz`とするための基準値
    /// * `max` - FizzBuzzゲームを繰り返し行う最大値
    pub fn new(fizz_num: u32, buzz_num: u32, max: u32) -> Manager {
        let fizz_buzz_num = fizz_num * buzz_num;
        Manager {
            fizz_num,
            buzz_num,
            fizz_buzz_num,
            max,
        }
    }

    /// 与えられた数値から回答を作成して返却します。
    ///
    /// # Arguments
    ///
    /// * `num` - 対象の数値
    ///
    /// # Return value
    ///
    /// 返却値は`num`によって以下の値をとります。
    ///
    /// * `fizz_num`で割り切れる数字であった場合は`Fizz`を返却します。
    /// * `buzz_num`で割り切れる数字であった場合は`Buzz`を返却します。
    /// * `fizz_num`及び`buzz_num`の両方で割り切れる値であった場合は`FizzBuzz`を返却します。
    /// * 上記以外の値で割り切れる値であった場合は`num`を返却します。
    pub fn make_answer(&self, num: u32) -> String {
        match num {
            num if num % self.fizz_buzz_num == 0 => String::from("FizzBuzz"),
            num if num % self.fizz_num == 0 => String::from("Fizz"),
            num if num % self.buzz_num == 0 => String::from("Buzz"),
            _ => num.to_string(),
        }
    }

    /// 1からmaxまでの値を対象にFizzBuzzを実行します。
    pub fn run(&self) {
        for i in 1..=self.max {
            println!("{}", self.make_answer(i));
        }
    }
}

FizzBuzzのゲーム部分を司ってもらおうということでManager構造体を作成しました。

  • Fizzとなる数字fizz_num、Buzzとなる数字buzz_num、繰り返す最大数maxを設定できるようにコンストラクタを作成してみました。
  • make_answer()メソッドにて数字がFizzなのかBuzzなのかFizzBuzzなのか判定します。
  • run()メソッドで1 〜 maxまでの値をmake_answerに渡し、判定結果を出力します。

なお、このManager構造体を使用するmain関数は以下のような形となります。

main.rs
extern crate fizz_buzz;

use fizz_buzz::Manager;

fn main() {
    Manager::new(3, 5, 100).run();
}

さてさてさて、これでmain.rsから処理を外出しできました。
せっかくなので単体テストも作ってしまいます。

単体テストを作る

チュートリアル色々調べてみたところ、Rustでは単体テストのテストコードはテスト対象のモジュールと同一ファイル内に記載することがお作法らしい?
testsディレクトリにテストコードファイルを置く(例:src/sample.rsに対するテストコードをtests/sample.rsに配置など)のはモジュール間の結合テストコードである場合との事です。
上記に従って、make_answer()メソッドのテストコードをsrc/lib.rsに作成します!以下となります!ドン!

src/lib.rs
#[cfg(test)]
mod make_answer {
    use super::*;

    #[test]
    fn return_fizz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("Fizz");
        assert_eq!(manager.make_answer(3), expected);
        assert_eq!(manager.make_answer(6), expected);
    }

    #[test]
    fn return_buzz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("Buzz");
        assert_eq!(manager.make_answer(5), expected);
        assert_eq!(manager.make_answer(10), expected);
    }

    #[test]
    fn return_fizz_buzz() {
        let manager = Manager::new(3, 5, 10);
        let expected = String::from("FizzBuzz");
        assert_eq!(manager.make_answer(15), expected);
        assert_eq!(manager.make_answer(30), expected);
    }

    #[test]
    fn return_number() {
        let manager = Manager::new(3, 5, 10);
        assert_eq!(manager.make_answer(2), String::from("2"));
        assert_eq!(manager.make_answer(4), String::from("4"));
    }
}

ここまでやってみて

  • 本当はrun()メソッドの実行についてもテストした方が良いのだろうなぁ…と思いつつも、返り値がなく標準出力するだけメソッドとして作ってしまったのでテストしづらい…これに関しては処理の仕方を変えた方が良さそう。
  • 他の方々がRustで作成されたFizzBuzzを拝見すると、機能を色々駆使されていて非常に面白いし勉強になる。
  • FizzBuzzで学ぶenumとDisplay, Fromトレイト - FizzBuzzの判定結果にenumを使用するだけでなく、そのenumにDisplay, Fromトレイトを実装されていてCoolである…取り入れてみよう!
  • Re:FizzBuzzから始めるRust生活 - 1日にひとつのFizzBuzzを紹介するというスタイルとの通り、1日毎にRustの様々な機能が実例として紹介されているので非常に勉強になります(本記事執筆中に拝見して、投稿するの恥ずかしくなったのは内緒)。5日目時点でFizzBuzzの判定をタプルを使用してシンプルでエレガントになったmatch式に衝撃。

簡単なFizzBuzzをベースとして、色々弄っていくのは面白いし、新しく学んだ言語の文法や機能の復習として非常に良いですね。他の方々のFizzBuzzを参考にどんどん魔改造していき、この記事に追記していきたいなぁと思います。

追記:FizzBuzzの実行結果をテストできるようにしてみた

FizzBuzzを実行するrun()メソッドですが、標準出力するだけのメソッドとして作ってしまったので、単純にテストがしづらい状態となっていました。
実行結果の文字列を返却するメソッドに修正し、自動テストできるようにしてしまいたいと思います。

src/lib.rs
impl Manager {
    // ~~ 省略 ~~

    /// 1からmaxまでの値を対象にFizzBuzzを実行します。
    /// 
    /// # Return value
    /// 
    /// `max`までのFizzBuzzの実行結果を返却します。
    /// * 値ごとに改行されます。
    /// 
    pub fn run(&self) -> String {
        let mut result = String::from("");
        for i in 1..=self.max {
            let answer = self.make_answer(i);
            result = match i {
                1 => answer,
                _ => format!("{}\n{}", result, answer),
            }
        }
        result
    }
}
// ~~ 省略 ~~

#[cfg(test)]
mod run {
    use super::*;

    #[test]
    fn result() {
        let manager = Manager::new(3,5,10);
        let expected = "1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz";
        assert_eq!(manager.run(), expected);
    }
}
3
1
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
3
1