LoginSignup
12
6

More than 5 years have passed since last update.

fizzbuzzを本気で解く(前編)

Posted at

fizzbuzzを小難しく解きたい

以前、カジュアルな面談のような場でおもむろにクイズを出題される、ということがありました。
なんてことはないfizzbuzz問題だったのですが、帰り道、フツフツと疑問が湧いてきました。

うーん。あれは一体、何を見ていたのだろう・・

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

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

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

自分のコード

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

(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は保証されているものとする)

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

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が使えるのかを見られていたのかもな・・

(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}

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

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

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

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) }

ワイルドな奴

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

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

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

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

余計に長くなった。

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

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

モンキーパッチとか

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をラッピングしてみるのも面白いかもしれない

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

選外

無駄に再帰を使ってみる

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)

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

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 第二版

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

12
6
0

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
12
6