読者です 読者をやめる 読者になる 読者になる

nekoTheShadow’s diary

技術ブログとして始めたはずが、読書&愚痴ブログになりました(´・ω・`)

「標準入力から1行ずつ受け取って、配列に格納する」という処理を短く書きたい。

ruby

 プログラミング・コンテスト的なものを解いていると、「標準入力から一行ずつ受け取り、配列に格納する」という処理を書くことがしばしば求められます。こういうとき、わたしは以下のように書いてきましたし、ごく一般的な方法であるとも思います。

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の存在判定が行われるので、非効率です。他人に見せたり、速度を限界まで追求したりする場合はひとつ上の例のほうが適切であると思います。