標準入力で渡された数値群をFixnumとして受け取る方法~mapとprocの入門を添えて~


この記事のサマリ

・標準入力で渡されたスペース区切りの整数群は、「inputs = gets.split.map(&:to_i)」とすれば、各要素に対してto_iで、数値化された状態で受け取ることができる
・もし、上記「&:to_i」の部分が説明できない人は、この記事を読むとわかるようになるはずです。

背景

paizaという、プログラムを書く技術を計測(課題がいくつかあって、それに対してコードを提出→採点という流れ)して、それを転職に利用できるという、プログラマーと企業とのマッチングを行うサイトがあり、情報収集や勉強がてら利用させていただいているのですが。。。
paizaの環境の都合上(オンラインでリアルタイム採点をするため)、標準入力で受け取ったテストデータが、期待通り動作するか?というのが、問題を解く大前提になっているます。そのため、必ず「標準入力を適切に受け取る」というコードが必要になります。
問題の中には、「スペース区切りで入力され、かつそれがすべて数値」ということも珍しくありません。そんな時、入力値をsplitして、さらにmap(&:to_i)として、文字列ではなく数値として受け取ることが多かったのですが、この(&:to_i)って部分を、ちゃんと説明できないなぁってことに気づいて、勉強がてら整理してみることにしました。

何も考えずに、とりあえず受け取る場合

たとえば、「標準入力から3つの数値(x,y,z)を受け取り、x * y + z の結果を標準出力に表示する」という例があったとします。

op1 , op2 , op3 = gets.split
puts op1.to_i * op2.to_i + op3.to_i

こんな感じで「標準入力から3つの引数を受け取って、計算して表示するイメージになるかと思いますが、getsで取得した値は、文字列ですので、数値計算をする場合は「to_i」を使ってあげる必要があります。3つくらいなら無視できる人もいるかもしれませんが、ちょっとイケてないですよね。。

ちょっとmapを使っておしゃれにしてみる

さっきの例では、やっぱりto_iがたくさんでてきていて冗長なので、排除したいですよね?
そんなとき、便利なのが、mapやcollectです。
「mapメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。collectメソッドの別名です。」とのことです。引用元:http://ref.xaio.jp/ruby/classes/array/map
mapを使うか、collectを使うかは、宗教論争のようなものがあるみたいなのですが、今回の場合は文字列を数値にマッピングするイメージになるので、mapを使った方が直観的だと思います。
素直にmapを使うとこんな感じで書けます。

op1 , op2 , op3 = gets.split.map{|v|
  v.to_i
}
puts op1 * op2 + op3

結構すっきりしましたね。標準入力で渡された値を、splitして、それらをすべてto_iしてあげた上で、op1 ,op2, op3に渡すようにしたため、to_iが1回だけになりました。

さらにすっきりさせましょう

先ほどは、mapメソッドに対して、{|v| v.to_i}のようなブロックを渡していましたが、ブロックの代わりにProcオブジェクトを渡すことができます。(Procというのは、ブロックをオブジェクト化したものです。)

たとえば、Proc.newでProcオブジェクトを生成し、それをmapに渡す場合は、以下のように記述することができます。

p = Proc.new{|v| v.to_i}
op1 , op2 , op3 = gets.split.map(&p)
puts op1 * op2 + op3

すみません。。。あんまりすっきりしませんでした。
この&pというのが、見慣れないかもしれません。リファレンス:ブロック付きメソッド呼び出しに、「to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェ クトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実 行され、Proc オブジェクトを返すことが期待されます。」という記載があります。
Procオブジェクトは、to_procを持っていますので、&をつけてブロック付メソッド(この場合はmap)を呼び出すことで、to_proc(この場合は、to_iになる)が評価されるという、流れになります。

さて、ここで重要なポイントが2つあります。
まず、参照先で言及している通り
1.「to_procメソッドを持つオブジェクトならば、~」とある通り、ProcやMethodでなければならないということはありません。ダックタイピング的に、to_procを持っていればよいという点。
2.実は「Symbol」クラスは、to_procメソッドを持っている点

です。

Symbol.to_procという魔法をかけて、さらに綺麗に

Symbolクラスは、to_procメソッドを持っているため、ブロック付きメソッド呼び出しとして渡せるということがわかりました。
では、Symbolオブジェクトのto_procは何をやってくれるのでしょうか?
それは、http://ref.xaio.jp/ruby/classes/symbol/to_procを参照すると、「to_procメソッドは、シンボルと同名のメソッドを使う手続きオブジェクト(Proc)を作成して返します。」とのことです。
つまり、
:to_aだろうが:to_iだろうが、:hogehogeだろうが、渡したら、to_aメソッド、to_iメソッド、hogehogeメソッドを呼び出そうとするというわけです。

なるほど、「ブロック付メソッドを呼び出すときは、to_procを持つオブジェクトを&つきで渡せる」ということと、Symbolオブジェクトは、to_procを持っていて、それが:to_iというシンボルならto_iメソッドを呼び出そうとするというのなら。。。

op1 , op2 , op3 = gets.split.map(&:to_i)
op1 * op2 + op3

と書けるわけですね。
なんとなく使っていてわかっていなかったのですが、これですっきりしました。
特に「:to_i」の魔法が感動的でした。
これを機会に、丸暗記してしまっているRubyのイディオムを、一つ一つ紐解いて、Rubyistとしての地力を向上させたいものです。