##はじめに
railsのテストについて学習している時、何気なく出てきたコードが読めなかったので、備忘録として記録します。
以下の記事について学習していました。
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」
何気なく出てきた、確率を求めるrubyのコードとそのテストコードの内容が初見で理解できなかったので、そのことについてです。
##問題のコード
class Lottery
KUJI = %w(あたり はずれ はずれ はずれ)
def initialize
@result = KUJI.sample
end
def win?
@result == 'あたり'
end
def self.generate_results(count)
Array.new(count){ self.new }
end
end
it '当選確率が約25%になっていること' do
results = Lottery.generate_results(10000)
win_count = results.count(&:win?)
probability = win_count.to_f / 10000 * 100
expect(probability).to be_within(1.0).of(25.0)
end
##処理の概要
KUJI = %w(あたり はずれ はずれ はずれ) で、あたりが一つの配列を作成。
initializeメソッドで、KUJI.sampleを実行して、ランダムにくじを引く、簡易的なおみくじプログラムです。
##コードについて不明点を明らかにする。
まず、最初にこの部分がわかりませんでした。
KUJI = %w(あたり はずれ はずれ はずれ)
これは、%記法というイディオムで配列を作成するものでした。こんな書き方があるのか、勉強になります。
Rubyで%記法(パーセント記法)を使う
続いてこちらのコードです。
def self.generate_results(count)
Array.new(count){ self.new }
end
クラスメソッドが定義されています。配列を使用する機会がなかったので、arrayオブジェクトについてこの機会に調べました。
new(size) {|index| ... } -> Array[permalink][rdoc][edit]
長さ size の配列を生成し、各要素のインデックスを引数としてブロックを実行し、各要素の値をブロックの評価結果に設定します。
ブロックは要素毎に実行されるので、全要素をあるオブジェクトの複製にすることができます。
ary = Array.new(3){|index| "hoge#{index}"}
p ary #=> ["hoge0", "hoge1", "hoge2"]
引用:singleton method Array.new
なるほど、こんな感じでしょうか。
引数'size'の値分の長さ(要素数)の配列を作成。
↓
各要素のインデックス毎にブロックを実行。
↓
ブロックの実行結果(戻り値)を要素の値に設定。
それでは、テストコードを確認します。以下のように記述されています。
results = Lottery.generate_results(10000)
先ほど学習した内容をもとに処理を確認します。
メソッドの引数の値は、10000です。メソッドの内部では、次のような処理が行われます。
Array.new(10000){self.new}
長さ(要素数)が10000の配列が作成される。
↓
インデックス毎にブロックが実行される。今回は10000回ブロックが実行されることになります。
ブロックでは、クラスオブジェクト(Lottery)をインスタンス化する処理が実行されます。
↓
インスタンス化と同時にインスタンスメソッドのinitializeメソッドが呼ばれます。
def initialize
@result = KUJI.sample
end
initializeメソッド内では、配列に対してsampleメソッドが実行され、配列から要素をランダムで返す処理が行われます。返ってきた値は、インスタンス変数の@resultに代入されます。
参考:instance method Array#sample
つまり、最初に用意したKUJI = %w(あたり はずれ はずれ はずれ)に対して、10000回sampleメソッドを実行して、結果を新たに、作成した配列に格納するという処理ということがわかりました。
最後に、テストコードのこの部分です。
win_count = results.count(&:win?)
10000回の試行結果を格納した配列に対して、countメソッドを実行していることはわかりますが、その先がわかりませんでした。
まず、インスタンスメソッドのcountメソッドについて確認します。
count -> Integer[permalink][rdoc][edit]
count(item) -> Integer
count {|obj| ... } -> Integer
レシーバの要素数を返します。
引数を指定しない場合は、レシーバの要素数を返します。このとき、要素数を一つずつカウントします。
引数を一つ指定した場合は、レシーバの要素のうち引数に一致するものの個数をカウントして返します(一致は == で判定します)。
ブロックを指定した場合は、ブロックを評価して真になった要素の個数をカウントして返します。
[PARAM] item:
カウント対象となる値。
引用:instance method Enumerable#count
レシーバーの要素数を返すメソッドですね。今回は、resultsがレシーバーになりますので、resultsの要素数を返すことになります。
また、引数を指定した場合は、引数に一致する要素数を返すとのことなので、 ブロックを指定した場合は、ブロックを評価して真になった要素の個数をカウントして返すとのことなので、今回は、”&:win?”の部分に一致するを評価した結果が真になる、要素数をカウントして返しているはずです。”win?”については、以下のように定義されています。
def win?
@result == 'あたり'
end
@resultが'あたり'であった場合にtrueを返すメソッドですね。
このメソッドに"&:"がくっついています。これは、何なのでしょうか?
###&:について
"&"と"メソッドのシンボル"の組み合わせのようです。
&は、手続きオブジェクト(Procオブジェクト)をブロックに変換する修飾子なのですが、手続きオブジェクトの代わりにメソッドを渡すこともできます。ここでは、シンボルで表されたメソッドを&修飾子に渡して実行しています。
ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。それを実現するのが手続きオブジェクト(Proc)です。それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡します。Proc の代わりにメソッドオブジェクト(Method)を渡すことも出来ます。この場合、そのメソッドを呼ぶ手続きオブジェクトが生成され渡されます。
to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。デフォルトで Proc、Method オブジェクトは共に to_proc メソッドを持ちます。to_proc はメソッド呼び出し時に実行され、Proc オブジェクトを返すことが期待されます。
詳しくは、以下に記載がありました。
Ruby: アンパサンドとコロン&:
記法について調べてみた
【Ruby】array.map(&:method)を理解する
参考にした記事でも解説されていましたが、
results.count(&:win?) は results.count{|r| r.win? } を表します。
配列resultsから要素を一つずつ取り出し、win?メソッドを実行し、trueが返った要素の数を数えています。
##まとめ
- %w(要素)で配列を作成できる。
- ブロックは、イテレータとも呼ばれ、要素を一つ一つ取り出して繰り返し行う処理(再帰的処理)を抽象化するためのもの
- Array.new(size){...}は、size分の長さの配列を作成し、そのインデックス毎にブロックの処理を実行するもの。
- &は、手続きオブジェクト(Procオブジェクト)をブロックに変換してメソッドに渡す。
- {&:method名}は、&とシンボルで表したメソッド名の組合せ
- &は、to_proc メソッドを持つオブジェクトであれば、修飾して引数としてメソッド渡すことができる。
- デフォルトで Proc、Method オブジェクトは共に to_proc メソッドを持つ。
##参考
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」
singleton method Array.new
Rubyで%記法(パーセント記法)を使う
instance method Array#sample
instance method Enumerable#count
Ruby: アンパサンドとコロン&:
記法について調べてみた
【Ruby】array.map(&:method)を理解する
[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか
ブロック付きメソッド呼び出し