ぶちのブログ

競プロとCTFが趣味なWebエンジニアのアウトプットの場

「アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた」のをワンライナーっぽく解いてみた(後半)

前半はこちら

コード

5. 電話帳作成問題

仕様はこちら

class NameIndex
  HASH_INDEX = {
    /\A[ァ-オヴ].*/ => '',
    /\A[カ-ゴ].*/ => '',
    /\A[サ-ゾ].*/ => '',
    /\A[タ-ド].*/ => '',
    /\A[ナ-ノ].*/ => '',
    /\A[ハ-ポ].*/ => '',
    /\A[マ-モ].*/ => '',
    /\A[ャ-ヨ].*/ => '',
    /\A[ラ-ロ].*/ => '',
    /\A[ヮ-ン].*/ => ''
  }.freeze

  def self.create_index(names)
    names.group_by do |name|
      HASH_INDEX.each do |key, value|
        break value if name.match(key)
      end
    end.map do |key, value|
      [key, value.sort]
    end.sort
  end
end

Enumerable#group_byメソッドが大活躍。返り値がいい感じになるように正規表現の入ったHashを用意しておくとサラっとかける。 sortの書き方が微妙なのでもっとスッキリできるかも。

6. 国民の祝日.csv パースプログラム

仕様はこちら

require 'csv'
require 'date'

class HolidayParser
  CSV_PATH = File.expand_path('path/to/国民の祝日.csv')

  def self.parse(csv_path = CSV_PATH)
    CSV.open(csv_path).to_a[2..-3].inject([]) do |arr, row|
      arr.concat(row.reverse.each_slice(2).map { |a| [Date.parse(a[0]), a[1]] })
    end.group_by { |key, _value| key.year }.sort.to_h { |key, value| [key, value.to_h] }
  end
end

一旦二次元配列として読んだ後、group_byを使って三次元配列にした後、to_hメソッドでネストされたハッシュに変換する。
Ruby2.6.0からto_hメソッドがブロックを受け取っていい感じにできるようになったことを活用しているので、以前のバージョンだと動かなさそう。

7. 行単位、列単位で合計値を求めるプログラム

class SumMatrix
  def self.sum(matrix)
    matrix.map { |row| row + [row.sum] }.transpose.map { |row| row + [row.sum] }.transpose.tap do |mat|
      $l = mat.last.last.to_s.length
    end.map do |row|
      row.map(&:to_s).each_with_object($l).map(&:rjust).join('|')
    end.join("\n")
  end
end

一番の問題児コード。formatを合わせるときに、一番右下の要素の文字数を数えなければいけない。
とりあえずtapで取り出したいが、ブロック内で定義した変数が、同じメソッドチェーン上であってもブロック外では使えない。
スコープをもっと広いインスタンス変数やグローバル変数を使えば動く。特殊変数の$1と見間違えそうで面白かったのでグローバル変数で書いてみた(最悪)。

もしくは、

class SumMatrix
  def self.sum(matrix)
    l = nil
    matrix.map { |row| row + [row.sum] }.transpose.map { |row| row + [row.sum] }.transpose.tap do |mat|
      l = mat.last.last.to_s.length
    end.map do |row|
      row.map(&:to_s).each_with_object(l).map(&:rjust).join('|')
    end.join("\n")
  end
end

のように、メソッド内で初期化しておけば動く。
ブロックの外側に同名のローカル変数があるときは、初期化をせずにそちらを参照してくれるので、tapの引数のブロック内で、メソッドの文脈でのローカル変数lが書き換えられる。
ちょっと面白い挙動。

8. ガラケー文字入力問題

問題はこちら。AOJの問題だったので競プロerとしてなんか嬉しい。

KEYS = [
  ['.', ',', '!', '?', ' '],
  %w[a b c],
  %w[d e f],
  %w[g h i],
  %w[j k l],
  %w[m n o],
  %w[p q r s],
  %w[t u v],
  %w[w x y z]
].freeze
SIZES = [5, 3, 3, 3, 3, 3, 4, 3, 4].freeze

Q = gets.to_i
Q.times do
  puts(
    gets.chomp.split('0').delete_if(&:empty?).map do |i|
      KEYS[i[0].to_i - 1][(i.length - 1) % SIZES[i[0].to_i - 1]]
    end.join
  )
end

この問題C++で書くのは少し大変そう。Rubyだと0で区切って配列にするのが一瞬でできるので、楽勝。

終わりに

割とRubyの良さを活かす書き方ができたかなと思ってます。書いていて非常に楽しい言語なので、みなさんもぜひRubyを使ってみてください。