rubyの基礎(7)ブロック

rubyではブロックという概念があるので、for文はほとんど使わないと言ってもいいぐらいです。

ブロックとは、一言で言えば、「引数としてメソッドに渡す処理のかたまり」です。ブロックの、do~endまでの範囲すべてが引数です。
または、{ } で囲む事でも同じ意味です。

例えば、

irb> 3.times do |i| x = i * 2
irb>  puts x
irb> end

と書くと、

0
2
4

となります。

上の例の、do~endがブロックです。
このブロックは、timesメソッドに引数として渡されています。timesメソッドは、このブロックを、0、1、2と数え上げながら、変数i(|で囲むことでブロック変数と言います)に入れて、ブロックのコードを実行しています。

ブロックは、繰り返し処理(イテレートと言います)を行うメソッドに渡して処理することが多いです。なのでfor文よりブロックでイテレート(繰り返し回す方法)を覚えた方がいいです。(正確には、繰り返しではないブロックもありますから、ブロック=繰り返し、ではありません)

例えば、次のように配列aのeachメソッドで、配列に対して繰り返すことができます。

irb> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb> a.each do |i|
irb*  if i % 2 == 0
irb>   puts i
irb>  end
irb> end
2
4

何を繰り返しているかというと、「配列の要素の数値に対して2で割った余りが0、つまり2で割り切れるときその値を表示する」ということをしています。

eachメソッドの役割は、配列の要素を最初から最後まで順番に取り出す、ということです。取り出して何をするかをブロックで指定しているのです。ここでは、2で割り切れるときのその数値を表示しています。
|i|はブロック引数で、今の場合、配列の各要素が順番に入ってきます。このiは変数なので、命名は小文字で始まれば何でも良いです。
また、ブロックの外で使った変数はブロック内で参照できます。

irb> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb> sum = 0 
=> 0
irb> a.each do |i|
irb*  if i % 2 == 0
irb>   sum += i
irb>  end
irb> end
> puts sum
6

変数sumはeachメソッドより前に宣言されていますが、eachのブロックの中で使われています。2で割り切れる要素の合計は2,4なので、6です。

なお、do~end は以降、{ } で書きます。

ブロックの入れ子も書けます。

irb> xyz = ["X","Y","Z"] 
=> ["X", "Y", "Z"]
irb> xyz.each { |c1|
irb*  xyz.each { |c2|
irb*   xyz.each { |c3|
irb*    puts c1 + c2 + c3
irb>   }
irb>  }
irb> }

結果は、XYZの組み合わせの全通り(3×3×3=27)が表示されるはずです。

ブロックの外で定義したメソッドをブロック内で呼び出して処理をすることもできます。

# 引数の2つの数値を足すメソッドを呼び出す
irb> def plus(a,b)
irb>  sum = a + b
irb>  puts sum
irb> end
=> :plus
irb> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb> a.each {|i|
irb*  plus i,100
irb> }
101
102
103
104
105

この使い方は実際のプログラミングでは頻繁に出てきます。逆に言うと、こう言うブロックの中でメソッドを呼び出して処理を書く癖を付けていくと、プログラミングが上達します。

続けて、ブロックを使う配列のイテレートメソッドの中で使用頻度の高いものを紹介します。これらは本当によく使います。家を作る(プログラミングする)上での便利な電動工具のような存在です。

mapとcollect
mapとcollectは配列の各要素に対してブロック内で評価された値を新しい配列に入れて返してくれます。 

10個の要素を持つ配列を用意する
irb> a = Array.new(10)
=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
irb> arr = a.map {|s|
irb*  "*"
irb> }
=> ["*", "*", "*", "*", "*", "*", "*", "*", "*", "*"]

mapメソッドで全要素に初期値として"*"を代入し、新しい配列を返してくれます。
「ブロック内の最後の評価値」が採用されるのはメソッドの中の最後の評価値が戻り値になるという考え方と同じです。

例えば次のようなコードを試してみてください。ブロック引数を使わないときは省略ができます。

i = -1
a = Array.new(10)
b = a.map{
  i += 1
}
p b # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

collectメソッドもmapと同じ働きです。

selectとfind_allとreject
selectとfind_allとrejectは配列の各要素に対してブロックを評価しその戻り値が真の要素を集めた配列を返してくれます。

irb> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb> arr = a.select { |i|
irb*  i % 2 == 0 
irb> }
=> [2, 4]

selectメソッドで2で割り切れる要素を集めた新しい配列を返してくれます。find_allメソッドも同じ働きです。rejectはselectの逆でブロックの戻り値が真になる要素を除外した配列を返します。

findとdetect
findとdetectは配列の各要素に対してブロックを評価しその戻り値が真の要素になった最初の要素を返してくれます。

irb> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb> b = a.find {|i|
irb*  i % 2 == 0
irb> }
=> 2

findメソッドでは2で割り切れる最初の要素を返してくれます。detectメソッドも同じ働きです。

injectとreduce
injectとreduceは配列の要素を最初から最後まで順番に取り出し繰り返す点はeachと同じなのです。が、反復の中で前回の演算結果を使うことができるメソッドです。返すのは演算結果です。(数値演算なら数値が返り、文字列操作なら文字列が返ります)ちなみにブロック内では次回に渡す演算結果を返すようにします。

irb> a = [1,2,3,4,5,6,7,8,9]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb> b = a.inject(0) { |result,i|
irb*  result + i 
irb> }
=> 45

結果は1から9まで順番に足し合わせた値になります。((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9
injectメソッドの引数で初回の値を与えて(上記の例では0)反復の中で前回値を使うことができます。reduceメソッドも同じ働きです。

injectを文字列で使う例では、

#範囲オブジェクトaからzのアルファベットを持つ配列
irb> a = "a".."z"
=> "a".."z"
irb> b = a.inject("") { |result,s|
irb*  result + s
irb> }
=> "abcdefghijklmnopqrstuvwxyz"

結果はaからzまでのアルファベット全ての文字を連結することになります。


この記事が気に入ったらサポートをしてみませんか?