たたみすぎる Array#flatten

Array#flatten は配列をたたむメソッドです。

ary = [  [1],  [2, [3], 4],  [5]  ]

p ary.flatten #=> [1, 2, 3, 4, 5]

このメソッドは便利に見えて、非常にはまりやすいメソッドです。このメソッドはたたみすぎるんです。上で言うと、[1, 2, [3], 4, 5] を返してほしいのです。


例えば、「ちょっと x と y の組を作りたいけど、いちいち Point クラスを作るのは面倒」というようなときに、横着して [x, y] という配列を使います。その後、「座標の配列の配列」から「座標の配列」を作ろうと思って flatten すると、なんと「x と y が順に並んだ配列」ができてしまいます。これはたまりません。

この例だけ見れば「横着すんな」で終わりそうですが、

  • そうは言っても使い捨てプログラムでは横着したいし、
  • 連想配列からハッシュを作るとき Hash.new の引数にするために flatten を使う (Hash.new(*ary.flatten)) も、ハッシュのキーや値が配列ではまる (参考) とか、
  • Array を継承したクラスもつぶされるので始末が悪い (Array を継承して HTML の抽象構文木クラスを作っていたらはまった経験あり)

など、都合の悪い例は山ほど考えつきます。逆に、「たためる限りたたみたい」という現状の flatten への需要がどういうときに発生するのか想像できません。


というわけで、普通は Haskell の concat のように 1 段だけたたみたいのです。現状ではしょうがないから inject を使って以下のように書きます。

p ary.inject([]) {|z, x| z + x } #=> [1, 2, [3], 4, 5]

でも僕は頭が悪いので、inject (や fold) を使うのに MP を 10 くらい消費した気分になります。


というところで先日気がついたのですが、ruby 1.9 では Array#flatten のたたむ数を引数として指定できます

p ary.flatten(1) #=> [1, 2, [3], 4, 5] 

これこそ求めていた機能です。できればデフォルトで 1 にしてほしい気もしますが、とりあえずそこまでは望みません。効率など無視してリストの変換ベースで Ruby のプログラムを書く人 (= 僕) は大喜びです。「1.9 に乗り換える 10 の理由」を僕が選ぶとしたら多分入選します。


という感動を経て、次くらいに不満に思っていた tail の提案に至りました。めでたしめでたし。


(追記) 「たたむ」じゃなくて「広げる」じゃないのかという信じられないほど正しいつっこみをいただきました。でもなぜか僕の中では「たたむ」というイメージです。なんでだろう?