13日の金曜日

年末年始はすっかりPCから離れてしまった。少しリハビリ。

13日の金曜日が存在しない年はない。じゃあ1年に何回あるのよというのをチェックするプログラムを考える。普通に二重ループにすれば済む話だけど、何となくinjectを使ってみたかった。で、使ってみたら、また使い方を間違えた。

最初に書いたのは、以下。

FRIDAY = 5
jason = 0
months = (1..12).to_a

(1902..2037).each do |year|
  months.inject(0) do |jason, mon|
    if Time.mktime(year,mon,13).to_a[6] == FRIDAY
      jason = jason + 1
    end
  end
  print year, ":", jason, "\n"
end

これはエラーで実行できない。なんか色々分かってないような気がする……。

injectメソッドで使うアキュムレータって、なんだ。jasonのようにブロックの外側にあるローカル変数を指定していいのか、それとも名前が衝突しないように「j」とかにしないといけないのか。

というブロックの基本について分かってない気がしてきたけど、それとは別にinjectの使い方を間違えていたのに気づいた。

injectは直前のブロックの実行結果を、次のブロックのアキュムレータに渡すという動作を繰り返すので、if文がTrueとならない場合にはnilが渡されてしまう。この結果、初期値としては0を渡しているのに、すぐにjasonがnilに変化してしまって、次にif文が成立したときにnilに対して+メソッドを呼んでしまって怒られる。なので、条件が成立しないときには、ちゃんと現在の値を次の処理に渡さないといけない。

FRIDAY = 5
jason = 0
months = (1..12).to_a

(1902..2037).each do |year|
  jason = 
  months.inject(0) do |j, mon|
    if Time.mktime(year,mon,13).to_a[6] == FRIDAY
      j + 1
    else
      j
    end
  end
  print year, ":", jason, "\n"
end

実行結果はこんな感じ。

1998:3
1999:1
2000:1
2001:2
2002:2
2003:1
2004:2
2005:1
2006:2
2007:2
2008:1
2009:3
2010:1
2011:1
2012:3
2013:2
2014:1
2015:3
:
:

気持ち悪い点が2つ。1つは、injectしたmonthsの最終的な評価値をprint文に渡せないこと。

print year, ":", months.inject(0) do |j, mon| ...

と書けたっていいじゃないかと思うんだけど、どうもダメらしい。ていうかRubyでprint文って何だったっけ……。

もう1つ気持ち悪いのは、年のほうはループしてて月のほうだけ配列的に扱ってること。両方とも配列として、2つの配列から1つの配列を生成してその配列をほげほげするほうが、ずっとすっきりする気がするのだけど、やり方がよく分からない。