Rubyに入門して5ヶ月が経ちました。
そこでまずatCoderというサイトでひたすら競技プログラミングの問題を解いて、Rubyの素振りをしました。
残念ながら比較的カンタンなA問題とB問題しか消化しなかったのですが、どちらもワンライナーで書けるようになってきたのでメモします。
ワンライナーとは
※ちなみに、セミコロンを挟んだような擬似的なワンライナーではありません。
ワンライナーではないコード
まず、ワンライナーではないコードを示します。
例えば、こんな問題があったとします。
4x4のマス目の盤面があります。
これが標準入力の形で与えられるので、180度反転させて標準出力しなさい。
入力例:
. . . .
. o o .
. x x .
. . . .
解答:
. . . .
. x x .
. o o .
. . . .
安直に書いたら、こんなコードになるかもしれません。
# 空の配列を用意。
table = []
# 入力を4行分受け取り、配列に格納。
4.times do |t|
table.push gets.chomp
end
# 配列の各要素を反転。
4.times do |t|
table[t].reverse!
end
# 配列自体を反転。
table = table.reverse!
# 標準出力。
puts table
あるいは、もう少しシンプルに書くとこうなるかもしれません。
table = []
4.times do |t|
table << gets.chomp.reverse
end
puts table.reverse
ワンライナーのコード
さて、上記のメソッドをワンライナー化していきます。
ポイントは__コレクション系のメソッド__を使うことです。mapかreduceがいいのですが、今回の例ではmapを使います。
さて、mapを使ったコードがこちらになります。
puts (0..3).to_a.map{gets.chomp.reverse}.reverse
以下、説明をします。
範囲(Range)クラス
まず、範囲クラスのインスタンスを作ります。
(0..3) # 範囲クラスをインスタンス化
Ruby 1.9.3 リファレンスマニュアル > ライブラリ一覧 > 組み込みライブラリ > Rangeクラス
範囲を使うのは、後述するようにmapを使うためです。
範囲の配列化
次に範囲を配列化します。
(0..3).to_a # [0, 1, 2, 3]
ちなみに、配列の要素はなんでも構いません。
結局配列化するなら、以下の方法でもいいかもしれません。
Array.new(4) # [nil, nil, nil, nil]
http://ref.xaio.jp/ruby/classes/array/new
今回の例では、盤面は4x4と決まっていましたが、ひょっとしたら5x5の場合もあるかもしれません。その場合は、以下のようにして配列を作成します。
(0..gets.to_i).to_i
# 盤の列数が標準入力で与えられるとする。
追記
コメントをいただきました。
Rangeの配列化は、to_aではなく以下のようにも実現できるようです。
(*0..3) # [0, 1, 2]
また、RangeはEnumerableを実装しているので、Enumerableの持つメソッドを使うことができます。
(0..3).map() # 配列化は不要
map関数で処理を行う
ここでようやくmapの登場です。
(0..3).to_a.map{ # ここに処理 }
[0, 1, 2, 3]という要素に対してそれぞれ、処理内容を実行します。なので、4回の処理が行われることになります。
今回の処理は、1行の入力を受けて反転させることです。
(0..3).to_a.map{gets.chomp.reverse}
map関数のポイントとして、map関数は新しく配列を作成して返す、というものがあります。
つまり、以下のコードのような挙動になります。
nums = [1, 2, 3, 4]
nums.map{|a| a * 2} # [2, 4, 6, 8]
上記では、4回ループが回ります。そして、その度にaにnumsの各要素が入り、aに対してa * 2が行われます。その結果を格納した配列が、最終的に返されるのです。
なので、以下のコードは、
(0..3).to_a.map{gets.chomp.reverse}
4回の標準入力をそれぞれ反転させた結果、それらを格納された配列を返します。
そして、その配列に対して、reverseをかけることで、最終的に180度回転したように出力することができます。
puts (0..3).to_a.map{gets.chomp.reverse}.reverse
何が嬉しいのか
ワンライナーで書くと何が嬉しいのでしょうか。
まず、頭を使います。if文やfor文をゴリゴリ書いても正解なのですが、頭を使ったほうが楽しいです!
肝となるmapやreduceの内容をどう書けばいいのか。それを考える時間は楽しいです。さらに、mapやreduceに持ち込むにはどうすればいいのか、というフェーズもあります。
また、mapはreduceは関数型チックであります。
ある配列を参考にして、新しい配列を返す、というのは__イミュータブル__で良いです。
イミュータブルに関しては、以下の記事がわかりや分かりやすかったです。
というわけで、mapやreduceを使いうこなせば、ちょっと賢いエンジニアになれるかもしれません。
注意
リーダブルコードにあるように、短いコードと良いコードはイコールではありません。
ワンライナーはエレガントで面白いものですが、この記事では実務で書くことを推奨するわけではありません。
また、筆者はRuby初心者です。
文中に誤りがあったり、より良いコードの書き方があればぜひご指摘下さると嬉しいです。
では、素敵な競技プログラミングライフを~!!