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つの配列を生成してその配列をほげほげするほうが、ずっとすっきりする気がするのだけど、やり方がよく分からない。