質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

3回答

2647閲覧

Ruby 問題

K_T_T_K

総合スコア231

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2014/11/24 16:59

Ruby初心者です。
まだRubyをはじめて本日で3日目の者です。
今までPHPで開発してきたのですが、Rubyを勉強することにしてみました。
FizzBuzzなどその他の問題をやりながら勉強しています。
今回は、Rubyで閏年問題プログラムを作ってみようと思い書いてみました。
以下コードです。

lang

1def days(year, month) 2 if (year % 4 == 0 && year % 400 == 0 || (year % 100 != 0)) 3 if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12) 4 return 31 5 elsif (month == 4 || 6 || 9 || 11) 6 return 30 7 elsif (month == 2) 8 return 28 9 end 10 else 11 if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12) 12 return 31 13 elsif (month == 4 || 6 || 9 || 11) 14 return 30 15 elsif (month == 2) 16 return 29 17 end 18 end 19end 20puts "年を入力してください" 21year = gets.to_i 22puts "月を入力してください" 23month = gets.to_i 24days = days(year, month) 25puts "#{year}年の#{month}月は#{days}日です。"

かなり冗長なコードで良くないアルゴリズムですが、これは正しいプログラムなのでしょうか?
間違っていたら添削としてアドバイスを頂たいと思っています。
また、アンサーの方が今回の閏年問題プログラムを作成した場合、どのように
コードを書いたか別解も載せて頂ければかと思います。
宜しくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ruby の学習という点では、自分でいろいろな条件判定を行うコードを書くのがよいのですが、
アプリケーションの中でつかうなら、既存ライブラリーをつかうほうが、バグがすくなくなります。

私なら次のようにしてしまいます。

lang

1# coding: utf-8 2require 'date' 3 4def days(year, month) 5 (Date.new(year, month, -1) - Date.new(year, month, 1)).to_i + 1 6end 7 8(2000..2001).each do |y| 9 (1..12).each do |m| 10 puts "#{y}年の #{m}月は #{days(y, m)} 日です。" 11 end 12end

実行結果は次のようになります。

2000年の 1月は 31 日です。 2000年の 2月は 29 日です。 2000年の 3月は 31 日です。 2000年の 4月は 30 日です。 2000年の 5月は 31 日です。 2000年の 6月は 30 日です。 2000年の 7月は 31 日です。 2000年の 8月は 31 日です。 2000年の 9月は 30 日です。 2000年の 10月は 31 日です。 2000年の 11月は 30 日です。 2000年の 12月は 31 日です。 2001年の 1月は 31 日です。 2001年の 2月は 28 日です。 2001年の 3月は 31 日です。 2001年の 4月は 30 日です。 2001年の 5月は 31 日です。 2001年の 6月は 30 日です。 2001年の 7月は 31 日です。 2001年の 8月は 31 日です。 2001年の 9月は 30 日です。 2001年の 10月は 31 日です。 2001年の 11月は 30 日です。 2001年の 12月は 31 日です

参考

ActiveSupport というライブラリーを使うなら、days_in_month というそのものズバリのメソッドがあります。
参考:

ページ下部にある show source をクリックするとソースコードをみることもできます。

投稿2014/11/24 22:05

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

年月と日数の関係は正しいものとして、話を進めていきます

書き換えたあとのコードをぱっと出すよりは過程も載せたほうが良いとおもって、かなり長くなっています
しかし、参考になれば幸いです


  • まず初めにRuby っぽい書き方に換えていきます。うるう年判定もライブラリのメソッドを利用します

※ 関数のなかでの return は明示的に値を返さなければならない場合を除いて 省略することが出来ます

lang

1require 'date' 2def days(year, month) 3 # if (year % 4 == 0 && year % 400 == 0 || (year % 100 != 0)) 4 if Date.leap?(year) 5 # if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12) 6 [1, 3, 5, 7, 8, 10, 12].include?(month) 7 31 8 # elsif (month == 4 || 6 || 9 || 11) 9 elsif [4, 6, 9, 11].include?(month) 10 30 11 # elsif (month == 2) 12 elsif month.equal(2) 13 28 14 end 15 else # .... 16 # .... 17end
  • 判定内容に名前を付けてあげます(この段の命名はもうすこし考えたほうがいいとは思います...)

lang

1def days31?(month) 2 [1, 3, 5, 7, 8, 10, 12].include?(month) 3end 4 5def days30?(month) 6 [4, 6, 9, 11].include?(month) 7end 8 9def days28?(month) 10 month.equal?(2) 11end 12 13def days29?(month) 14 days28?(month) 15end 16 17def days(year, month) 18 if Date.leap?(year) 19 if day31?(month) 20 31 21 elsif days30?(month) 22 30 23 elsif days28?(month) 24 28 25 end 26 else 27 if days31?(month) 28 31 29 elsif days30?(month) 30 30 31 elsif days29?(month) 32 29 33 end 34 end 35end
  • ひとつ前の書き換えで、フクザツに見えていた重複点が見易くなりました。ここでさらに、case when に書き換えてみます

lang

1require 'date' 2 3def days(year, month) 4 case month 5 when 1, 3, 5, 7, 8, 10, 12 6 30 7 when 4, 6, 9, 11 8 31 9 when 2 10 Date.leap?(year) ? 28 : 29 11 end 12end 13 14def display_day_of_month 15 puts '年を入力してください' 16 year = gets.to_i 17 puts '月を入力してください' 18 month = gets.to_i 19 days = days(year, month) 20 puts "#{year}年の#{month}月は#{days}日です。" 21end

以上で、コードがだいぶスッキリした見た目になったと思います

長くなりましたが:

  • フクザツな処理(ここでの、うるう年判定)はライブラリにお任せする
  • 変数のみならず、様々なものに名前を付けてあげる(あるいは解説コメントを付ける)
  • 関数は10行前後に留める

という点を意識して、ここまで書き換えてみました

Links

投稿2014/11/24 19:22

gouf

総合スコア2321

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

K_T_T_K

2014/11/25 00:18

回答ありがとうございます。 大変丁寧わかりやすい説明ありがとうございました。 参考になります。 ベストアンサーにさせていただきます。
guest

0

... ruby の学習という点では、自分でいろいろな条件判定を行うコードを書くのがよいのですが ...

ライブラリーに頼らず、自分で条件判定をするようなコードを書いてみました。
utils.rb と 動作の簡易チェックのための utils_test.rb の2つを以下に示します。
(test も rspec を使わずに書いてみました。
yard などで API ドキュメントを生成した時に、適切なドキュメントが生成されうようなコメントを
いれることもしなければいけまsんが、今回はそこはサボりました)

$ rubocop utils.rb でのコードスタイルのチェックでも 変な警告は出ないようにしてあります。
(gem install rubocop すると rubocop コマンドが使えるようになります)

utils.rb

lang

1# coding: utf-8 2 3# 便利メソッドを定義する。 4module Utils 5 # 閏年でないときの各月の日数 6 NORMAL_DAYS = { 7 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 8 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 9 }.freeze 10 11 # year 年 month 月の日数を求める 12 def days(year, month) 13 check_year_month(year, month) # year, month の値範囲をチェックする。 14 15 # 閏年の2月は 29 日まである。 16 if month == 2 && leap?(year) 17 29 18 else 19 NORMAL_DAYS[month] 20 end 21 end 22 23 # year が閏年かを調べる。 24 def leap?(year) 25 (year % 4 == 0) && ((year % 400 == 0) || (year % 100 != 0)) 26 end 27 28 # year, month の値範囲をチェックする。 29 def check_year_month(year, month) 30 if (year < 1900) || (2038 < year) || (month < 1) || (12 < month) 31 throw ArgumentError.new("#{year}, #{month}") 32 end 33 end 34end 35# --- End of File ---

utils_test.rb

lang

1# coding: utf-8 2 3require File.join(File.expand_path(File.dirname(__FILE__)), 'utils.rb') 4 5#--------- test ------ 6include Utils 7 8TESTS = [ 9 [2000, 1, 31], 10 [2000, 2, 29], 11 [2000, 3, 31], 12 [2000, 4, 30], 13 [2000, 5, 31], 14 [2000, 6, 30], 15 [2000, 7, 31], 16 [2000, 8, 31], 17 [2000, 9, 30], 18 [2000, 10, 31], 19 [2000, 11, 30], 20 [2000, 12, 31], 21 22 [2001, 1, 31], 23 [2001, 2, 28], 24 [2001, 3, 31], 25 [2001, 4, 30], 26 [2001, 5, 31], 27 [2001, 6, 30], 28 [2001, 7, 31], 29 [2001, 8, 31], 30 [2001, 9, 30], 31 [2001, 10, 31], 32 [2001, 11, 30], 33 [2001, 12, 31] 34] 35 36TESTS.each do |t| 37 ans = Utils.days t[0], t[1] 38 fail "#{t.join(' ')} != #{t[0]} #{t[1]} #{ans}" unless ans == t[2] 39end 40 41ERR_TESTS = [ 42 [nil, nil, "undefined method `<' for nil:NilClass"], 43 [2000, nil, "undefined method `<' for nil:NilClass"], 44 [nil, 1, "undefined method `<' for nil:NilClass"], 45 46 [1899, 12, 'uncaught throw #<ArgumentError: 1899, 12>'], 47 [2039, 1, 'uncaught throw #<ArgumentError: 2039, 1>'], 48 49 [2000, 0, 'uncaught throw #<ArgumentError: 2000, 0>'], 50 [2000, 13, 'uncaught throw #<ArgumentError: 2000, 13>'] 51 52] 53ERR_TESTS.each do |t| 54 begin 55 Utils.days t[0], t[1] 56 fail 'エラーが発生しませんでした。' 57 rescue => ex 58 raise ex.to_s + t.join(' ') if ex.to_s != t[2] 59 end 60end 61 62# $ rubocop days.rb 63# $ ruby days_test.rb

投稿2014/11/25 16:19

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

K_T_T_K

2014/11/25 16:41

Katoyさん いつも詳しい回答ありがとうございます! 大変参考になります! また、Rubocopを教えて頂ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問