Ruby1.8.7でインデックス付きmapを使う

mapをインデックス付きで使いたい!と思ってRuby1.8.7Ruby1.9ライクな書き方をしようとしたら失敗した。
Rubyでは内部イテレータが使われている。

[1,2,3].each { |n| print n }

イテレータにインデックスを渡すこともできる。

[1,2,3].each_with_index { |n,i| puts "#{i}: #{n}" }

ところでRuby 1.9からeachやmapはブロックを与えられなかった場合に外部イテレータを返すようになった。これにより、こんな書き方もできるようになった。

e = [1,2,3].each
e.with_index { |n,i| puts "#{i}: #{n}" }

これで何が嬉しいって、mapでも簡単にインデックス番号を使えるようになったのである。*1

[1,2,3].map.with_index { |n,i| [i,n*2] } #=> [[0,2],[1,4],[2,6]]

そして、Ruby1.8.7Ruby1.9からのバックポートを多く含み、その中には外部イテレータもあるから、もちろん同じことができる…と思ったのだが。

NoMethodError: undefined method `with_index' for [1, 2, 3]:Array

あるえぇぇ?

実はRuby1.8.7のmapは互換性を維持するために旧来どおりに動作する、つまり外部イテレータを返さない。

じゃあどうするかというと、eachは外部イテレータを返すので、それを前に出せばいいわけですな。

[1,2,3].each_with_index.map { |n,i| [i,n*2] }

…そんな話が、今から約二年前にruby-devで解決されてました!

orz

*1:私はcollectよりもmap派。