Rubyの研修その3 - Rubyっぽい書き方に挑戦してみた
Ruby勉強中の僕が、Rubyっぽい書き方に挑戦してみた話です。
この記事で使っているRubyのバージョンは2.1.4です。
この記事は前回の記事の続きになります。取り組んだ課題の内容も似ていますし、やっていることも前回の記事の終盤と同じです。
今回与えられた課題はこのようなものでした。
偉い人「タブ区切りテキスト、meibo.txtがあります。名前、性別、年齢の順に並んでいます。meibo.txtを年齢順に並び替えて出力してください」
meibo.txt
john m 18 paul m 20 alice f 15 dabid m 17 jasmin f 17
meibo.txtは前回の課題と同じです。これを、年齢でソートして出力してあげればいいわけです。
この課題に対して、僕が最初に書いたコードはこんなコードでした。
sort1.rb
data = [] STDIN.each do |line| data.push(line.split) end data.sort_by! do |line| line[2] end for line in data do for num in 0..(line.length - 1) do if num == line.length - 1 then print line[num] else print line[num], "\t" end end print "\n" end
うーん、今見直すと最高に鈍臭いですね。後半の二重のforループのところが特にひどいです。今になって言ってみると、なんだかC言語っぽい書き方です。
一応何をやっているのか解説します。まず、空の配列dataに、テキスト一行を空白文字で分割して得られた配列を順次プッシュします。次に、配列dataを年齢でソートします。最後に、ループを回して配列dataの中身を、適宜タブや改行を入れながら出力します。
これを提出したところ、返ってきた答えはこのようなものでした。
偉い人「出力するときにループを回すんじゃなくて、joinを使ってもっとスタイリッシュにしてみて」
どうやら出力するときにjoinを使ってあげると、ループを回す必要がなくなり、スタイリッシュになるようです。さっそくjoinについて調べてみます。
ふむふむ。配列を指定した区切り文字で結合した文字列を返すメソッドのようです。前回初めて使って、今回も使ったsplitとは、逆の操作をしていると考えればいいのでしょうか。
joinについてわかったところで、さっそく先ほどのsort1.rbをスタイリッシュに書き直してみました。
sort2.rb
data = [] STDIN.each{|line| data.push(line.split) } data.sort_by!{|line| line[2] } data.each{|line| puts line.join("\t") }
変わったのは出力する部分だけですが、大幅にスタイリッシュになりました。さっきは出力する部分を10行も書いていましたが、今回のコードでは3行になっています。
気分よくこれを提出したところ、ちょっと意外な答えが返ってきました。
偉い人「dataを使わなくても書けるんじゃないかな。グローバルな変数を使わないで書けるはず」
なんと、dataを使わなくても書けるそうです。ここから先は偉い人のアドバイスを適宜受けながら進めていきました。
そして完成したコードがこちらです。
sort3.rb
puts STDIN.map{|line| line.split }.sort_by{|line| line[2] }.map{|line| line.join("\t") }.join("\n")
前回の記事の最後みたいなコードになりました。いきなりputsから始まっているところや、mapが返した配列についてsort_by(前回はinject)しているところが、前回と共通しています。
こうやって書くと、スコープの広い変数を使っていないので、変数の初期化忘れや、どこかで変数が汚される心配がなく、バグが発生しにくいというメリットがあるようです。lineという名前の変数が3回登場しますが、これらのスコープは全てそのブロックの終わりまでなので、その3つのlineは全て異なるlineです。
こういう書き方をするかしないかは好みの問題だそうですが、せっかくRubyを書いているので、こういう書き方にもどんどん慣れ親しんでいきたいと思います。