Help us understand the problem. What is going on with this article?

Ruby de FizzBuzz

More than 1 year has passed since last update.

はじめに

この記事はFizzBuzz Advent Calendar 2017の 12/24 に向けて書いたものです。

もちろん、FizzBuzz Advent Calender に Ruby版が無い!!
ということで書きました。

Rubyとは

Rubyとは...
オープンソースの動的なプログラミング言語で、 シンプルさと高い生産性を備えています。 エレガントな文法を持ち、自然に読み書きができます。(公式サイトより)

FizzBuzz

プログラミング能力をみるテストに使われるFizzBuzzは以下のように使われます。

1から順番に数値を出力します。ただし、下記の条件をみたすとき、数値の代わりに指定した文字列を出力します。

  • 3で割り切れるとき "Fizz"
  • 5で割り切れるとき "Buzz"
  • 3と5で割り切れるとき "FizzBuzz"

簡単なんだけど、焦っていると間違えてしまいがちですね〜

実装

要求仕様

1 から n までの数値について FizzBuzz して、 fn に書き込むメソッドを作成します。

fizzbuzz(n, fn)

テスト

まずはテストを書きましょう。
1 から 100 までの期待する答えを spec/fizz_buzz.answer に作ります。その後、rspec ファイルを作成します。

# spec/fizz_buzz_spec.rb
require_relative '../fizz_buzz.rb'

describe :FizzBuzz do
  before :all do
    dir = File.expand_path(File.dirname(__FILE__))
    @_read  = ->(fn){ File.open(fn).map{ |r| r.chomp }}
    @answer = @_read[File.join(dir, 'fizz_buzz.answer')]
  end

  it 'should be eauql to anser' do
    fn = 'fizz_buzz.result'
    fizzbuzz(100, fn)
    expect(@_read[fn]).to match_array @answer
  end
end

下記コマンドでテストします。

$ rspec spec

最初の実装

最初の実装なので、とりあえず動かします。

def fizzbuzz(n, fn)
  File.open(fn,'w') do |f|
    1.upto(n) do |i|
      out = ''
      i % 3 == 0 and out << 'Fizz'
      i % 5 == 0 and out << 'Buzz'
      f.puts out.empty?? i : out
    end
  end
end

文字列演算を避ける

文字列演算は重いので、避けてみました。

def fizzbuzz(n, fn)
  File.open(fn,'w') do |f|
    1.upto(n) do |i|
      f.puts case
      when i % 15 == 0 then 'FizzBuzz'
      when i % 3  == 0 then 'Fizz'
      when i % 5  == 0 then 'Buzz'
      else i
      end
    end
  end
end

ハッシュを使う

割り算が3つもあるので、なんとかしたいです。
ということで、ハッシュを使ってみます。

class FizzBuzz
  CONDITION = { 0 => 'FizzBuzz' }
  { 3 => 'Fizz',
    5 => 'Buzz',
  }.each do |no, key|
    no.step(14,no){ |i| CONDITION[i] = key }
  end

  class << self
    public
    def fizzbuzz(n, fn)
      File.open(fn,'w') do |f|
        1.upto(n) do |i|
          f.puts CONDITION[i % 15] || i
        end
      end
    end
  end
end

# wrapper
def fizzbuzz(n, fn)
  FizzBuzz.fizzbuzz(n, fn)
end

wrapper は要求仕様に従うためのものですが、変更が可能であればその方が良いですね。

ベンチマーク

せっかく作ったので、ベンチマークを取ってみます。
メソッド名を適当に変えます。あと、wrapper も外します。

require 'benchmark'

n  = 10000000
fn = 'fizzbuzz.out'
Benchmark.bm do |r|
  r.report('no 1'){ fizzbuzz1(n, fn) }; File.delete(fn)
  r.report('no 2'){ fizzbuzz2(n, fn) }; File.delete(fn)
  r.report('no 3'){ FizzBuzz.fizzbuzz(n, fn) }; File.delete(fn)
end

結果は以下の通りでした。

       user     system      total        real
no 1  5.810000   0.160000   5.970000 (  6.045857)
no 2  5.120000   0.150000   5.270000 (  5.311798)
no 3  4.850000   0.130000   4.980000 (  4.990330)

目論見どおり、早くなりました。
よかった...

おわりに

Testドリブンな開発を FizzBuzz を例にして行った形になりました。
Ruby が好きになってくれると嬉しいです。

ohto
trying to solve mathematical problems
https://www.facebook.com/ohtoya
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