除算(/)・剰余算(%)を使わない FizzBuzz - Ruby 編

プログラミング経験者なら一度は通る FizzBuzz。
このお題に「除算(/)・剰余算(%)禁止」の制約を設けると、面白さが格段にアップします。
様々な解決アプローチを考えることで頭の体操になりますし、語彙を増やすきっかけにもなります。

以下、自分の考えたアプローチをいくつか紹介します（Ruby 2.0.0 以降で動作）。
他にも「こんなやり方あるよ！」というのがあれば是非教えてください。

# 1. 飛び飛びで値を格納していくアプローチ

## 1.1 `Integer#step` の利用

空の `Array` を定義し、3個飛ばしで `'Fizz'` を格納し、5個飛ばしで `'Buzz'` を格納or追記します。
隙間の要素は `nil` で埋められるので、要素が `nil` の場合は整数を出力します。

```ruby:fizzbuzz_step_array.rb
num = 100
list = []
3.step(num, 3) {|i| list[i] = 'Fizz' }
5.step(num, 5) {|i| list[i] = list[i].to_s + 'Buzz' }

# list の中身: [ nil, nil, nil, 'Fizz', nil, 'Buzz',
#              'Fizz', nil, nil, 'Fizz', 'Buzz', ... ]

(1..num).each {|i| puts list[i] || i }
```

## 1.2 `Hash` を使った改良版
空の `Hash` に `'Fizz'`, `'Buzz'`, `'FizzBuzz'` を出力する要素を格納していきます。
それ以外の要素は `Hash#fetch` のデフォルト値として取得します。

`Hash` では要素番号が飛び飛びになる場合でも隙間の要素が `nil` で埋められることはないため、
`Array` よりも要素数を少なくできます。

```ruby:fizzbuzz_step_hash.rb
num = 100
list = {}
3.step(num, 3) {|i| list[i] = 'Fizz' }
5.step(num, 5) {|i| list[i] = list[i].to_s + 'Buzz' }

# list の中身: { 3 => 'Fizz',  6 => 'Fizz',  9 => 'Fizz', ...　,
#               5 => 'Buzz', 10 => 'Buzz', 15 => 'FizzBuzz', ... }

(1..num).each {|i| puts list.fetch(i, i) }
```

`Hash#fetch` の代わりに `Hash` のデフォルト値を使う方法もありますが、記述が冗長になるため割愛します。

# 2. 周期的構造を利用するアプローチ

## 2.1 15進数表現の利用
`Integer#to_s` は、第一引数を指定することで2〜36進数の文字列表現を返します。
このメソッドを利用して、15進数表現の最下位桁で FizzBuzz 判定します。

```ruby:fizzbuzz_15-ary.rb
class Integer
  def to_fizzbuzz
    case to_s(15)[-1]
    when *%w(3 6 9 c)
      'Fizz'
    when *%w(5 a)
      'Buzz'
    when *%w(0)
      'FizzBuzz'
    else
      self
    end
  end
end

# main
num = 100
(1..num).each {|i| puts i.to_fizzbuzz }
```

## 2.2 `Enumerator` オブジェクトの利用
`each` メソッドは、ブロックを省略すると `Enumerator` オブジェクトを返します。
`Enumerator` オブジェクトには `#next` や `#rewind` など、列挙状態を手動で1つ先に進めたり巻き戻したりするメソッドが定義されています。

```ruby:fizzbuzz_enumerator.rb
# 以下のようにも書ける
# * fizz_enum = [nil, nil, 'Fizz'].to_enum
# * fizz_enum = Enumerator.new([nil, nil, 'Fizz'])
fizz_enum = [nil, nil, 'Fizz'].each
buzz_enum = [nil, nil, nil, nil, 'Buzz'].each

num = 100
(1..num).each do |i|
  (fizz = fizz_enum.next) && fizz_enum.rewind
  (buzz = buzz_enum.next) && buzz_enum.rewind
  puts (fizz || buzz) ? "#{fizz}#{buzz}" : i
end
```

## 2.3 `cycle` メソッドによる `Enumerator` を使った改良版

`each` でなく `cycle` メソッドによる `Enumerator` オブジェクトを利用すれば、
`#rewind` の記述が不要になります。

```ruby:fizzbuzz_cycle_enumerator.rb
# 以下のようにも書ける
# * fizz_cycle = [nil, nil, 'Fizz'].to_enum(:cycle)
# * fizz_cycle = Enumerator.new([nil, nil, 'Fizz'], :cycle)
fizz_cycle = [nil, nil, 'Fizz'].cycle
buzz_cycle = [nil, nil, nil, nil, 'Buzz'].cycle

num = 100
(1..num).each do |i|
  fizz = fizz_cycle.next
  buzz = buzz_cycle.next
  puts (fizz || buzz) ? "#{fizz}#{buzz}" : i
end
```

# X. ネタ的アプローチ
## X.1 スリープソート的なやつ
1, 3, 5, 15秒ごとにそれぞれ `整数`, `'Fizz'`, `'Buzz'`, `'FizzBuzz'` を標準出力するスレッドを同時実行します。

 * 100個の出力に100秒かかります
 * うっかり `3` とか出力したあとにこっそり `Fizz` とかに書き換えます（`\r` で）
    * タイミングの問題でたまに出力間違えますが許してあげてください
    * 整数が9999超えると書き換え漏れしますが許して(ry

```ruby:fizzbuzz_timer.rb
class FizzBuzzTimer
  def initialize(num)
    @num = num
  end

  def start
    running_threads = [
      integer_thread,
      fizz_thread,
      buzz_thread,
      fizzbuzz_thread,
    ]
    sleep(@num)
  ensure
    running_threads.each(&:kill)
  end

  private

  def integer_thread
    num = 0
    Thread.new do
      loop do
        print num += 1
        sleep(1)
        print "\n"
      end
    end
  end

  def fizz_thread
    Thread.new do
      loop do
        sleep(3)
        print "\rFizz"
      end
    end
  end

  def buzz_thread
    Thread.new do
      loop do
        sleep(5)
        print "\rBuzz"
      end
    end
  end

  def fizzbuzz_thread
    Thread.new do
      loop do
        sleep(15)
        print "\rFizzBuzz"
      end
    end
  end
end

# main
num = 100
FizzBuzzTimer.new(num).start
```

# 他にもあれば随時追記