プログラミング・コンテスト的なものを解いていると、「標準入力から一行ずつ受け取り、配列に格納する」という処理を書くことがしばしば求められます。こういうとき、わたしは以下のように書いてきましたし、ごく一般的な方法であるとも思います。
lines = [] while line = gets do lines << line.to_i end p lines #=> [1, 2, 3, 4]
が、個人的にこれが気に入らない。とくにwhile
のブロックが1行である点。Rubyには後置while
があるので、繰り返し中の処理が1行ならこれを使いたいのです。その方がコード全体が短くなるのはもちろん、可読性も高いと思います。そこで次のように書き換えてみるわけですが……
lines = [] lines << line.to_i while line = gets p lines #=> ERROR
残念ながら上の例ではエラーです。line
なる変数が定義されていないと怒られてしまいます――ううむ。弱った。これを何とか打開できないかと、いろいろ試行錯誤していたところ、意外なところから解決策を発見! やったぜ。
リクルートキャリア社が企画運営するエンジニア転職サイトCodeIQの企画で、LTSVというテキストデータのフォーマット形式を扱ったものが最近出題されました。さっそくこれに解答しようと、LTSVの公式ページをのぞいたところ、パーサの例として次のような記述が。
while gets record = Hash[$_.split("\t").map{|f| f.split(":", 2)}] p record end
注目したいのは$_
。不思議に思って調べてみたところ、これは組み込み定数のひとつで、読み込んだ文字列のうち、最新のものを格納するそうです。
「これを使えば後置whileを使えるのでは……?」ということで、実際にやってみました。
lines = [] lines << $_.to_i while gets p lines #=> [1, 2, 3, 4]
大成功!――ですが、ここまで短くしたのなら、もっと短くしたいという衝動に駆られます(よね?)。
(lines ||= []) << $_.to_i while gets p lines #=> [1, 2, 3, 4]
これで実質1行で、目的の処理を達成することができました。もっとも、ここまでやってしまうと、正直いってわかりづらい。また、push
が行われるたびに、変数lines
の存在判定が行われるので、非効率です。他人に見せたり、速度を限界まで追求したりする場合はひとつ上の例のほうが適切であると思います。