« Haskell で「エラトステネスの篩」 その2 | トップページ | 平方根の求め方 その2 〜 今度は Haskell で »

2011年1月 3日 (月)

FizzBuzz 再び : Integer#times の落とし穴

 昨日、「fizzbuzz問題」関係のブログをいくつか見ていたら、Ruby 初心者さん(プログラミング初心者さん)がたまにハマる勘違いを見つけちゃいました。(「fizzbuzz問題」に関してはこちらを参照のこと。普通は 1 〜 100 までの範囲を表示させることが多いですね)

 問題の記事はこちらです。コードを引用してみます(読み易いようにインデントさせました)

100.times{|n| if n % 15 == 0 puts "fizzbuzz" elsif n % 3 == 0 puts "fizz " elsif n % 5 == 0 puts "buzz " else puts "#{n}" end }
 実際にこのコードを走らせると次のようになります。
>ruby test.rb ruby test.rb fizzbuzz 1 2 fizz 4 buzz fizz < 中略 > 94 buzz fizz 97 98 fizz
 皆さんは間違いに気付きましたか?

 この記事を書いた方は「1 〜 100」までの範囲を表示させるつもりで 100.times{|n| .. } と書いています。ところがこう書くと「0 〜 99」までの整数が小さいほうから順に n に代入されていきます。つまり、「100回数える」んだけど、始まりは「0」からなんですね。
 そのため 1 の前に fizzbuzz が表示され、本当は最後に表示されるべき buzz が表示されていません。

 Ruby の Integer#times に限らず、プログラミング言語には「0」から始まるものがたくさんあります。例えば Ruby や C 言語の配列のインデックスや Scheme や Haskell のリストのインデックスも「0」から始まります。
 普通、物を数える時は「1」から数え始めますよね。ところが「0」から数え始めたほうがコンピュータには都合がいいらしいんですね。

 ではこの問題では Integer#times は使えないかというと、そうではありません。ちょっと工夫すれば題意どおりの表示ができます。(以後のコードはすべてメソッドの形にしてあります)

# Integer#times 修正版 def fizzbuzz1 100.times do |m| n = m + 1 if n % 15 == 0 puts "fizzbuzz" elsif n % 3 == 0 puts "fizz " elsif n % 5 == 0 puts "buzz " else puts "#{n}" end end end

 ただ、今回のような場合には Integer#times よりも、開始点と終了点が明示できる Integer#upto を使ったほうが分かり易いと思ます。(elsif を並べるのが好きではないので case を使いました)

# Integer#upto を使う def fizzbuzz2 1.upto(100) do |n| case when n % 15 == 0 puts "fizzbuzz" when n % 3 == 0 puts "fizz" when n % 5 == 0 puts "buzz" else puts n end end end

 さて、話はここで終ってもいいのですが、Ruby には「多様性は善」という言葉もありますから、他の方法も考えてみました。ネットで探せばもっとたくさんの方法が見付かると思います。

# while を使う def fizzbuzz3 n = 1 while (n <= 100) case when n % 15 == 0 puts "fizzbuzz" when n % 3 == 0 puts "fizz" when n % 5 == 0 puts "buzz" else puts n end n += 1 end end # Range#each を使う def fizzbuzz4 (1..100).each do |n| case when n % 15 == 0 puts "fizzbuzz" when n % 3 == 0 puts "fizz" when n % 5 == 0 puts "buzz" else puts n end end end # Range#map を使う def fizzbuzz5 arr = (1 .. 100).map do |n| case when n % 15 == 0 "fizzbuzz" when n % 3 == 0 "fizz" when n % 5 == 0 "buzz" else n end end puts arr end # 剰余演算子を使わない 1 def fizzbuzz6 fizz = ["", "", "fizz"].cycle.take(100) buzz = ["", "", "", "", "buzz"].cycle.take(100) (1 .. 100).zip(fizz, buzz) do |a, b, c| if b == "" && c == "" puts a else puts b + c end end end # 剰余演算子を使わない 2 def fizzbuzz7 nums = (0 .. 100).to_a a = [[3, "Fizz"], [5, "Buzz"], [15, "FizzBuzz"]] a.each{|arr| 0.step(100, arr[0]){|i| nums[i] = arr[1]}} puts nums.drop(1) end

※なお、すべてのコードは Ruby 1.9 を対象に書いてあります。Ruby 1.8 では動かないものがあるかもしれません。

« Haskell で「エラトステネスの篩」 その2 | トップページ | 平方根の求め方 その2 〜 今度は Haskell で »

Ruby」カテゴリの記事

コメント

初めまして
スクリプトを間違えていたものの張本人ですw

記事を読んでなるほどと思いました。
全く気づいてなかったです(´・ω・`)・・・
勉強になりました(`・ω・´)シャキーン

お役に立てたようで、良かったです。

コンピュータは融通が利かないので、「0か1か」の違いがバグの元になったりすることもあります。

プログラミング言語が人間に使いやすい様に進化していても、まだまだコンピュータの都合も考えてあげなくてはいけないので、慣れるまでは大変ですよね。

コメントを書く

コメントは記事投稿者が公開するまで表示されません。

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/112020/50484859

この記事へのトラックバック一覧です: FizzBuzz 再び : Integer#times の落とし穴:

« Haskell で「エラトステネスの篩」 その2 | トップページ | 平方根の求め方 その2 〜 今度は Haskell で »

2016年7月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
フォト

最近のトラックバック

無料ブログはココログ