20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Ruby】map関数をマスターして、素敵な競技プログラミング生活を

Last updated at Posted at 2016-05-20

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などの配列の値を操作するメソッドまとめ

さて、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は関数型チックであります。
ある配列を参考にして、新しい配列を返す、というのは__イミュータブル__で良いです。
イミュータブルに関しては、以下の記事がわかりや分かりやすかったです。

Immutableの利便性、大きなメリットについて。

というわけで、mapやreduceを使いうこなせば、ちょっと賢いエンジニアになれるかもしれません。

注意

リーダブルコードにあるように、短いコードと良いコードはイコールではありません。
ワンライナーはエレガントで面白いものですが、この記事では実務で書くことを推奨するわけではありません。

また、筆者はRuby初心者です。
文中に誤りがあったり、より良いコードの書き方があればぜひご指摘下さると嬉しいです。

では、素敵な競技プログラミングライフを~!!

20
18
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?