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を作ってみたい。
参考
この本を読みながら様々な角度からfizzbuzzを書いてみると、色々な技術が身につく気がしました。ええ。たぶん。