fizzbuzzを本気で解く(前編)

# fizzbuzzを小難しく解きたい

以前､カジュアルな面談のような場でおもむろにクイズを出題される､ということがありました｡
なんてことはない[fizzbuzz問題](https://ja.wikipedia.org/wiki/Fizz_Buzz)だったのですが､帰り道､フツフツと疑問が湧いてきました｡

うーん｡あれは一体､何を見ていたのだろう･･

その時は無難なコードを書いて終わったのですが､あれは貴重なアピールの場だったかも知れないなと反省｡

こういう局面において､**｢どんなfizzbuzzを書いたら強いインパクトを残せるのか｣**という間違った路線を考えた結果､最終的にfizzbuzz専用のDSLを作ることになりました｡
(思いのほか長くなったので､DSLは次回)

本当の面接では無難な答えがいいのかもと思いますw
たぶん｡まあ､こんな簡単な問題を聞かれることは稀だと思うけど｡

# 自分のコード

最初にぱっと思いついたのはこんな感じ

```ruby
(MIN_NUM..MAX_NUM).each do |n|
  if n % 15 == 0
    puts = 'fizzbuzz'
  elsif n % 3 == 0
    puts = 'fizz'
  elsif n % 5 == 0
    puts = 'buzz'
  else
    puts = n.to_s
  end
end
```

ちなみに､MIN_NUMからMAX_NUMまで計算する設定
(MAX_NUM > MIN_NUMは保証されているものとする)

もうちょっとルビー感を出したかったので､大体こんなところで手を打った

```ruby
MIN_NUM.upto MAX_NUM do |n|
  s = ''
  s << 'fizz' if (n % 3).zero?
  s << 'buzz' if (n % 5).zero?
  s = n.to_s if s.empty?
  puts s
end
```

こんなのでよかったのだろうか｡

でもまてよ｡さらっとmapが使えるのかを見られていたのかもな･･

```ruby
(MIN_NUM..MAX_NUM).to_a.map { |n|
  m = n
  n = 'fizz' if (m % 3).zero?
  n = 'buzz' if (m % 5).zero?
  n = 'fizzbuzz' if (m % 15).zero?
  n.to_s
}.each{|s| puts s}
```

ちょっとトリビアっぽい使い方も見せられたかもしれない｡

```ruby
a = (MIN_NUM..MAX_NUM).to_a.map {|n|
  case [n % 3, n % 5].map(&:zero?)
  when [true, true]
    n='fizzbuzz'
  when [true, false]
    n='fizz'
  when [false, true]
    n='buzz'
  else
    n=n
  end
}
puts *a
```

まっとうに､条件設定と処理を書き分けるとかもいいのかも｡

```ruby
rule = { 15: 'fizzbuzz', 3: 'fizz', 5: 'buzz' }

def fizzbuzz(n)
  rule.each.do |m, comment|
    return comment if (n % m).zero?
  end
  return n.to_s
end

(MIN_NUM..MAX_NUM).to_a.each{ |s| puts fizzbuzz(s) }
```

# ワイルドな奴

この辺から段々こじらせてきた｡

```ruby
devidable = -> (n, m) {(m % n).zero?}
fizzbuzz = -> (s, b) {b ? s : nil}
MIN_NUM.upto MAX_NUM do |n|
  s = nil
  s ||= fizzbuzz.call(devidable.call(n,15), 'fizzbuzz')
  s ||= fizzbuzz.call(devidable.call(n,3), 'fizz')
  s ||= fizzbuzz.call(devidable.call(n,5), 'buzz')
  s ||= n.to_s
  puts s
end
```

共通部分が気になるので､カリー化してリファクタリング｡

```ruby
devidable =-> (m, n) { (n % m).zero? }

db_curried = devidable.curry
db15 = db_curried.(15)
db3 = db_curried.(3)
db5 = db_curried.(5)

MIN_NUM.upto MAX_NUM do |i|
  case i
  when db15
    puts 'fizzbuzz'
  when db3
    puts 'fizz'
  when db5
    puts 'buzz'
  else
    puts i
  end
end
```

余計に長くなった｡

まさかの例外処理を使ってみる

```ruby
MIN_NUM.upto MAX_NUM do |n|
  begin
    1/(n % 15)
  rescue
    puts 'fizzbuzz'
    next
  end
  begin
    1/(n % 3)
  rescue
    puts 'fizz'
    next
  end
  begin
    1/(n % 5)
  rescue
    puts 'buzz'
    next
  end
  puts n
end
```

モンキーパッチとか

```ruby
module FizzBuzzExtentions
  refine Fixnum do
    def fizzbuzz
      n = self
      n = 'fizz' if (self % 3).zero?
      n = 'buzz' if (self % 5).zero?
      n = 'fizzbuzz' if (self % 15).zero?
      n.to_s
    end
  end
end

module FizzBuzz
  using FizzBuzzExtentions
  (MIN_NUM..MAX_NUM).each {|s| puts s.fizzbuzz}
end
```

メソッド`puts`をラッピングしてみるのも面白いかもしれない

```ruby
module PutsWrapper

  def puts n
    if n.is_a? Fixnum
      if (n % 15).zero?
        super 'fizzbuzz'
      elsif (n % 3).zero?
        super 'fizz'
      elsif (n % 5).zero?
        super 'buzz'
      else
        super n
      end
    else
      super n
    end
  end
end

Object.class_eval do
  prepend PutsWrapper
end
(MIN_NUM..MAX_NUM).each {|s| puts s}
```

｢面倒くせーやつだ｣｢一緒に働きたくない｣と思われること間違いなしでしょうww

# 選外

無駄に再帰を使ってみる

```ruby
def fizzbuzzR(n,max)
  return if n>max
  if (n % 15).zero?
    puts 'fizzbuzz'
  elsif (n % 3).zero?
    puts 'fizz'
  elsif (n % 5).zero?
    puts 'buzz'
  else
    puts n.to_s
  end
  fizzbuzzR(n+1, max)
end
fizzbuzzR(MIN_NUM, MAX_NUM)
```


全くルビーぽくないコードで逆アピールするとか

```ruby
n = MIN_NUM

while n<=MAX_NUM do

  mod3 = (n % 3) == 0 ? 1 : 0
  mod5 = (n % 5) == 0 ? 1 : 0

  case 2 * mod5 + mod3
  when 0
    puts n
  when 1
    puts 'fizz'
  when 2
    puts 'buzz'
  when 3
    puts 'fizzbuzz'
  end

  n += 1
end
```

あとは､｢3の倍数は各桁の数字同士を足し､これを1桁になるまで繰り返して行くと3､6､9のいずれかになる｣という性質や､｢5の倍数は1桁目が5,0で終わる｣という特徴を利用して､途方もない労力をかけて倍数を判定する方向など･･･(割愛)

というわけで､もっと面白いアイデアがあればぜひご教示くださいませ｡

後編はDSLを作ってみたい｡

# 参考

[メタプログラミングruby 第二版](https://www.amazon.co.jp/%E3%83%A1%E3%82%BF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Ruby-%E7%AC%AC2%E7%89%88-Paolo-Perrotta/dp/4873117437)

この本を読みながら様々な角度からfizzbuzzを書いてみると､色々な技術が身につく気がしました｡ええ｡たぶん｡