わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

続々・満年齢の計算

慣習と法律とで,満年齢に違いが現れる日(生年月日と基準日のペア)を求めるプログラムを作りなさい.生年月日と基準日の範囲は,ハードコード(プログラムの中で決め打ち)してよい.

続・満年齢の計算 - わさっき

前のプログラムを修正します.まず

if __FILE__ == $0

の行を

if __FILE__ == $0 and false

として,実行しないようにします*1.そして,以下のコードを追加します.

if __FILE__ == $0
  ac = Age.new
  birth_min = Date.new(1999, 1, 1)
  birth_max = Date.new(2000, 12, 31)
  today_min = Date.new(2004, 1, 1)
  today_max = Date.new(2005, 12, 31)

  birth_min.upto(birth_max) do |b|
    today_min.upto(today_max) do |t|
      ac.birth = b
      ac.today = t
      ac.method = Age::METHOD_CONVENTION
      age_c = ac.calc_age
      ac.method = Age::METHOD_LAW
      age_l = ac.calc_age
      if age_c != age_l
        puts "birth=#{b}, today=#{t}, age_c=#{age_c}, age_l=#{age_l}"
      end
    end
  end
end

生年月日は1999年1月1日から2000年12月31日まで,基準日は2004年1月1日から2005年12月31日まで.閏年とそうでない年を入れました.それで2重ループです.Dateインスタンスに対するuptoは,1日1行カレンダー - わさっきでも使用しました.
実行結果のうち,2月28日近辺を抜き出してみます.

birth=1999-02-28, today=2004-02-27, age_c=4, age_l=5
birth=1999-02-28, today=2005-02-27, age_c=5, age_l=6
birth=1999-03-01, today=2004-02-29, age_c=4, age_l=5
birth=1999-03-01, today=2005-02-28, age_c=5, age_l=6
(略)
birth=2000-02-28, today=2004-02-27, age_c=3, age_l=4
birth=2000-02-28, today=2005-02-27, age_c=4, age_l=5
birth=2000-02-29, today=2004-02-28, age_c=3, age_l=4
birth=2000-02-29, today=2005-02-28, age_c=4, age_l=5
birth=2000-03-01, today=2004-02-29, age_c=3, age_l=4
birth=2000-03-01, today=2005-02-28, age_c=4, age_l=5

たぶん合っているはず.
それにしても,すべて求めるのに,3分以上かかりました.(365+366)の2乗は,534361回.ノートPCのファンが回って,途中,不安になりました.
コードを見直してみましょう.そもそも注意しないといけないのは,誕生日近辺です.だから,基準日を,生年月日の4年後と5年後のプラスマイナス何日かのみに限定することにします.生年月日のループは,1999年1月1日から2000年12月31日までで変えません.
上の追加コードの「if __FILE__ == $0」はまた「if __FILE__ == $0 and false」としてから,新たなコードです.

if __FILE__ == $0
  ac = Age.new
  birth_min = Date.new(1999, 1, 1)
  birth_max = Date.new(2000, 12, 31)

  birth_min.upto(birth_max) do |b|
    [4, 5].each do |i|
      (-3 .. 3).each do |j|
        ac.birth = b
        ac.today = b + (365 * i + j)
        ac.method = Age::METHOD_CONVENTION
        age_c = ac.calc_age
        ac.method = Age::METHOD_LAW
        age_l = ac.calc_age
        if age_c != age_l
          puts "birth=#{b}, today=#{ac.today}, age_c=#{age_c}, age_l=#{age_l}"
        end
      end
    end
  end
end

そして実行.今度は4秒ほどで終わりました*2.2月28日の周辺を取り出してみます.

birth=1999-02-28, today=2003-02-27, age_c=3, age_l=4
birth=1999-02-28, today=2004-02-27, age_c=4, age_l=5
birth=1999-03-01, today=2003-02-28, age_c=3, age_l=4
birth=1999-03-01, today=2004-02-29, age_c=4, age_l=5
(略)
birth=2000-02-28, today=2004-02-27, age_c=3, age_l=4
birth=2000-02-28, today=2005-02-27, age_c=4, age_l=5
birth=2000-02-29, today=2004-02-28, age_c=3, age_l=4
birth=2000-02-29, today=2005-02-28, age_c=4, age_l=5
birth=2000-03-01, today=2004-02-29, age_c=3, age_l=4
birth=2000-03-01, today=2005-02-28, age_c=4, age_l=5

基準日を「生年月日の4年後近辺と5年後近辺」に変更したので,1999年生まれの出力は,前のと異なっています.しかし2000年生まれの出力は同じですし,この修正版でも,「誕生年が閏年か否か」と「基準年が閏年か否か」であわせて4通りについて,2月28日前後のチェックが行えています.生年月日と基準日はプログラマ(私)の都合で決めたもので,結局のところ,「満年齢に違いが現れる日(生年月日と基準日のペア)を求める」という目的のもとではうまくいっているわけです.

*1:各行「#」にするとか,「=begin」と「=end」ではさむとかよりも,修正の手間,そしてコードを復帰させるときの手間も少ないので,if文ブロック全体を処理しない方法として,おすすめです.

*2:ただこれでも,ac.todayに代入されるDateインスタンスの生成に無駄があります.配列に格納して,冗長な生成をなくすようにすると,実行時間は約2.5秒まで減りました.