みなさん、こんにちは!
筆者はRuby on RailsでRails7の初期 + ruby 3.2 環境ぐらいまでは書いていましたが、ここ半年は専らMisskeyの開発などに勤しんでいるのもあり、TypeScriptばかりを書く日々を送っていました。
そんなある日、ふとQiitaを開いてみるとpaiza×Qiita記事投稿キャンペーン「プログラミング問題をやってみて書いたコードを投稿しよう!」なんてキャンペーンをやってたので、久々にrubyでも書こうと思い、記事を書くことにしました。
対象の問題
久々にrubyでも書くので手堅くみんな大好きFizzBuzz問題でも解いてみようと思います。
プログラミング問題としては定番なのでご存じの方も多いでしょうが、簡単に問題を説明すると、
- 1~100までの数字Nが与えられる
- 各数字ごとに、1からNまでの数字のうち次を出力する
- 3の倍数ならば "Fizz"
- 5の倍数ならば "Buzz"
- 3の倍数かつ5の倍数ならば "Fizz Buzz"
- いずれにも当てはまらなければ 数字N
といった問題です。
この後模範解答を紹介しますが、そんなの簡単だろという方はぜひ実際に書いたコードまでジャンプしましょう。
おそらく想定される一般的な解き方
模範解答は大事です。プログラミングの繰り返しと条件分岐が学べる基本問題ですね。
# まずは数字を取得
N = gets.to_i
# 1~N まで 繰り返しで順番に条件をチェックし条件に応じて出力
(1..N).each do |i|
if i % 3 == 0 && i % 5 == 0
puts "Fizz Buzz"
elsif i % 3 == 0
puts "Fizz"
elsif i % 5 == 0
puts "Buzz"
else
puts i
end
end
実際に書いたコード
FizzBuzzの解き方という初歩的な課題で、ありふれた模範解答を載せてもインターネット上に大量の例が載っているので面白くないと思います。特に記事投稿用ということで目の肥えたQiita読者の方も楽しんでいただけたらいいなと思います。
コンセプト
- コードを圧縮可能
- rubyのEnumerableの良さを生かす
- ちょっと捻ったコード
実物
outs = { 15 => 'Fizz Buzz', 5 => 'Buzz', 3 => 'Fizz' }
(1..gets.to_i).each { |i| puts i if (outs.none? { |k,v| (puts v;true) if i % k.to_i == 0 }) }
たった2行です。
ここまで圧縮されていると解説もしにくいので少し読みやすくして見ましょう。
(rubyでは一般的には可読性のためにeachやifはdo ~ end
で書かれますがこのように書くことも可能です。)
解説
outs = { 15 => 'Fizz Buzz', 5 => 'Buzz', 3 => 'Fizz' }
(1..gets.to_i).each do |i|
if outs.none? { { |k,v| (puts v;true) if i % k.to_i == 0 } }
puts i
end
end
まずはouts
を宣言しています。これはそれぞれの倍数に対して出力する文字列をHashとして持たせています。rubyは文字列、Symbol、数字など色々とHashのキーとして扱えます。
冒頭に15
という数字を直で書いてあります。
一見わかりにくいかもしれませんが、これは3の倍数かつ5の倍数である数字が最小公倍数である15の倍数であるという性質を利用しています。
また、rubyでのHashは1.9以降、順番を保持されるようになっています。(2009年に1.9がリリースされたみたいなので執筆時点でももう15年が経ってます。すごい)
次に、模範解答と同じようにループ処理をしています。
ループ処理の中のif文の条件に注目してください。
RubyのHashはEnumerableのメソッドが使えます。その中にnone?
というメソッドがあるのですが、これが非常に重要な役割を果たしています。
ブロックを指定した場合は、Enumerable オブジェクトのすべての要素をブロックで評価した結果が、すべて偽であれば真を返します。そうでなければ偽を返します。
ブロック内で15, 5, 3
についてそれぞれ割り切れるかどうかを確認し、割り切れる場合は対応する文字列を出力しtrueを返し、そうでない場合は何も返しません。Hashの順序は保証されているので実行順は問題ないです。
何も返らない場合、rubyでは偽と評価されるため、もし割り切れる数字がない場合にはnone?
で評価した値が偽(false
), 割り切れる値がある場合には真(true
)となります。
そのため、割り切れる数字がない場合はif文の中身の処理が走り、もともとの文字列を出力することになります。
さいごに
やはりこういった基礎の問題は普通に解くのも楽しいですが、言語固有の便利なメソッドや特性を利用して工夫して解くのもまた楽しさがあります。
皆さんもたまには利用する言語のドキュメントなどを眺めていると、こういった便利メソッドなどに出会えるかもしれません。Rubyは日本生まれなこともあり公式ドキュメントが日本語で非常に読みやすいですね。
それでは、よいコーディングライフを。