paizaで戦うために便利なRubyのメソッド


paizaとは

paiza はリクルーティング・サービスで、応募者のレベルを測るのに競技プログラミングを使っているのが特徴です。paizaはコーディングの結果をもとにユーザーを6段階でランク付けします。Topcoderのような他のメジャーな競技プログラミングサービスと違って、多くの言語 をサポートしていて、この業界では人気のないRubyもサポートしています。

当方普段Railsを使っているので、コーディング言語としてRubyを選びました。下記はその中で学んだテクニックです。

String#ljust, String#rjust

paizaではしばしば出力のフォーマットを要求されます。Rubyには複雑なこともできるsprintfもありますが、多くの場合String#ljust, String#rjustで十分です。

入力

2
apple banana orange
1 2 3

期待される出力

apple     banana    orange    
1         2         3         

良い例

total_rows = gets.to_i
db = []
total_rows.times { db << gets.chomp.split }

db.each do |line|
  # 文字列が10文字より小さければスペースで埋める
  formatted = line.map { |str| str.ljust(10) }
  puts formatted.join
end

Array#transpose

データベースのそれぞれのカラムの合計を返す問題を考えましょう。
ラベルと行数、各行のデータが与えられて、期待される出力はカラム名と合計です。

入力

apple banana orange
5
1 2 3
4 5 6
7 8 9
1 2 3
4 5 6

期待される出力

apple 17
banana 22
orange 27

Rubyの強みの一つにメソッド・チェーンがあります。もし要求が同じ行の値の合計なら簡単に実装できます。
残念ながらpaizaは水平方向の計算を好みます。 競技プログラミングではコンソールに出力をだすという制約があるので問題に難しさを足すのに便利なやり方なのかもしれません。以下は配列のインデックスを使った例です。

悪い例

label = gets.chomp.split
total_rows = gets.to_i
db = []
total_rows.times { db << gets.split.map(&:to_i) }
result = []
# Rubyは列操作が苦手
(0...label.size).each do |i|
  sum = 0
  db.each { |line| sum += line[i] }
  result << sum
end

(0...label.size).each do |i|
  puts "#{label[i]} #{result[i]}"
end

このコードは動くものの、自分はもっと複雑な問題で何度かインデックスを数え間違えました。個人的な意見ですが、C風のインデックスの多用は自然なRubyの使い方でない気がします。

Rubyの配列には行と列をいれかえる transpose というメソッドがあります。これを上手く使うときれいに書けエラーを防げます。

良い例

label = gets.chomp.split
total_rows = gets.to_i
db = []
total_rows.times { db << gets.split.map(&:to_i) }
result = []
# tranposeを使って簡単に表現
db.transpose.each { |arr| result << arr.reduce(&:+) }

[label, result].transpose.each do |pair|
  puts pair.join(' ')
end